toiljs 0.0.5 → 0.0.6

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.
Files changed (83) hide show
  1. package/build/cli/.tsbuildinfo +1 -1
  2. package/build/cli/create.js +75 -61
  3. package/build/client/.tsbuildinfo +1 -1
  4. package/build/client/channel/channel.d.ts +23 -0
  5. package/build/client/channel/channel.js +94 -0
  6. package/build/client/head/head.d.ts +26 -0
  7. package/build/client/head/head.js +87 -0
  8. package/build/client/index.d.ts +17 -17
  9. package/build/client/index.js +10 -10
  10. package/build/client/navigation/Link.d.ts +8 -0
  11. package/build/client/navigation/Link.js +44 -0
  12. package/build/client/navigation/NavLink.d.ts +14 -0
  13. package/build/client/navigation/NavLink.js +37 -0
  14. package/build/client/navigation/navigation.d.ts +13 -0
  15. package/build/client/navigation/navigation.js +97 -0
  16. package/build/client/navigation/prefetch.d.ts +11 -0
  17. package/build/client/navigation/prefetch.js +100 -0
  18. package/build/client/navigation/scroll.d.ts +8 -0
  19. package/build/client/navigation/scroll.js +36 -0
  20. package/build/client/routing/Router.d.ts +7 -0
  21. package/build/client/routing/Router.js +55 -0
  22. package/build/client/routing/error-boundary.d.ts +16 -0
  23. package/build/client/routing/error-boundary.js +19 -0
  24. package/build/client/routing/hooks.d.ts +17 -0
  25. package/build/client/routing/hooks.js +48 -0
  26. package/build/client/routing/lazy.d.ts +16 -0
  27. package/build/client/routing/lazy.js +53 -0
  28. package/build/client/routing/match.d.ts +2 -0
  29. package/build/client/routing/match.js +32 -0
  30. package/build/client/routing/mount.d.ts +2 -0
  31. package/build/client/routing/mount.js +13 -0
  32. package/build/client/routing/params-context.d.ts +2 -0
  33. package/build/client/routing/params-context.js +2 -0
  34. package/build/compiler/.tsbuildinfo +1 -1
  35. package/build/compiler/config.js +10 -1
  36. package/build/compiler/generate.js +18 -8
  37. package/build/compiler/plugin.js +14 -0
  38. package/examples/basic/client/components/HoneycombBackground.tsx +162 -0
  39. package/examples/basic/client/layout.tsx +12 -8
  40. package/examples/basic/client/public/favicon.ico +0 -0
  41. package/examples/basic/client/public/index.html +2 -1
  42. package/package.json +2 -2
  43. package/src/cli/create.ts +100 -73
  44. package/src/client/index.ts +17 -17
  45. package/src/client/{NavLink.tsx → navigation/NavLink.tsx} +1 -1
  46. package/src/client/{prefetch.ts → navigation/prefetch.ts} +2 -2
  47. package/src/client/{Router.tsx → routing/Router.tsx} +3 -3
  48. package/src/client/{error-boundary.tsx → routing/error-boundary.tsx} +1 -1
  49. package/src/client/{hooks.ts → routing/hooks.ts} +2 -2
  50. package/src/client/{lazy.ts → routing/lazy.ts} +1 -1
  51. package/src/client/{mount.tsx → routing/mount.tsx} +3 -3
  52. package/src/compiler/config.ts +11 -2
  53. package/src/compiler/generate.ts +24 -8
  54. package/src/compiler/plugin.ts +19 -0
  55. package/templates/app/client/404.tsx +11 -0
  56. package/templates/app/client/components/.gitkeep +1 -0
  57. package/templates/app/client/components/Footer.tsx +8 -0
  58. package/templates/app/client/components/HoneycombBackground.tsx +162 -0
  59. package/templates/app/client/layout.tsx +53 -0
  60. package/templates/app/client/public/favicon.ico +0 -0
  61. package/templates/app/client/public/images/.gitkeep +1 -0
  62. package/templates/app/client/public/images/logo.svg +37 -0
  63. package/templates/app/client/public/index.html +16 -0
  64. package/templates/app/client/public/robots.txt +2 -0
  65. package/templates/app/client/routes/about.tsx +11 -0
  66. package/templates/app/client/routes/blog/[id].tsx +12 -0
  67. package/templates/app/client/routes/docs/[...slug].tsx +12 -0
  68. package/templates/app/client/routes/get-started.tsx +84 -0
  69. package/templates/app/client/routes/index.tsx +80 -0
  70. package/templates/app/client/routes/io.tsx +24 -0
  71. package/templates/app/client/styles/main.css +461 -0
  72. package/templates/app/client/toil.tsx +7 -0
  73. package/test/channel.test.ts +1 -1
  74. package/test/head.test.ts +1 -1
  75. package/test/navlink.test.ts +1 -1
  76. package/test/routes.test.ts +1 -1
  77. /package/src/client/{channel.ts → channel/channel.ts} +0 -0
  78. /package/src/client/{head.ts → head/head.ts} +0 -0
  79. /package/src/client/{Link.tsx → navigation/Link.tsx} +0 -0
  80. /package/src/client/{navigation.ts → navigation/navigation.ts} +0 -0
  81. /package/src/client/{scroll.ts → navigation/scroll.ts} +0 -0
  82. /package/src/client/{match.ts → routing/match.ts} +0 -0
  83. /package/src/client/{params-context.ts → routing/params-context.ts} +0 -0
@@ -1,5 +1,6 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import Footer from './components/Footer';
3
+ import HoneycombBackground from './components/HoneycombBackground';
3
4
 
4
5
  const GitHubIcon = () => (
5
6
  <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
@@ -10,26 +11,29 @@ const GitHubIcon = () => (
10
11
  export default function Layout({ children }: { children?: ReactNode }) {
11
12
  return (
12
13
  <div className="app">
14
+ <HoneycombBackground />
13
15
  <Toil.Head
14
- titleTemplate="%s · ToilJS"
16
+ titleTemplate="%s By Dacely"
15
17
  title="ToilJS"
16
18
  meta={[{ name: 'description', content: 'The most performant React framework.' }]}
17
19
  />
18
20
  <header className="nav">
19
- <a href="/" className="nav-logo">
21
+ <Toil.Link href="/" className="nav-logo">
20
22
  <img src="images/logo.svg" alt="ToilJS" width={28} height={28} />
21
23
  <span>ToilJS</span>
22
- </a>
24
+ </Toil.Link>
23
25
 
24
26
  <nav className="nav-center">
25
- <Toil.NavLink href="/" end className="nav-center-link">Home</Toil.NavLink>
26
- <Toil.NavLink href="/get-started" className="nav-center-link">Get Started</Toil.NavLink>
27
+ <Toil.NavLink href="/" end className="nav-center-link">
28
+ Home
29
+ </Toil.NavLink>
30
+ <Toil.NavLink href="/get-started" className="nav-center-link">
31
+ Get Started
32
+ </Toil.NavLink>
27
33
  </nav>
28
34
 
29
35
  <nav className="nav-links">
30
- <Toil.Link href="https://toil.org/docs">
31
- Docs
32
- </Toil.Link>
36
+ <Toil.Link href="https://toil.org/docs">Docs</Toil.Link>
33
37
  <a
34
38
  href="https://github.com/btc-vision/toiljs"
35
39
  target="_blank"
@@ -4,7 +4,8 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <meta name="theme-color" content="#080D11" />
7
- <title>toiljs · basic</title>
7
+ <link rel="icon" type="image/x-icon" href="/favicon.ico" />
8
+ <title>ToilJS By Dacely</title>
8
9
  <link rel="preconnect" href="https://fonts.googleapis.com" />
9
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
11
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@700;800;900&display=swap" rel="stylesheet" />
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "toiljs",
3
3
  "type": "module",
4
- "version": "0.0.5",
4
+ "version": "0.0.6",
5
5
  "author": "Dacely",
6
6
  "description": "todo",
7
7
  "engines": {
@@ -102,7 +102,7 @@
102
102
  "eslint-plugin-react-hooks": "^7.1.0-canary-ab18f33d-20260220",
103
103
  "eslint-plugin-react-refresh": "^0.5.2",
104
104
  "picocolors": "^1.1.1",
105
- "toilscript": "^0.1.2",
105
+ "toilscript": "^0.1.4",
106
106
  "typescript-eslint": "^8.60.0",
107
107
  "vite": "^8.0.14",
108
108
  "vite-plugin-node-polyfills": "^0.28.0"
package/src/cli/create.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import fs from 'node:fs/promises';
7
7
  import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
8
9
 
9
10
  import {
10
11
  intro,
@@ -25,6 +26,7 @@ import {
25
26
  PKG_VERSION,
26
27
  PREPROCESSORS,
27
28
  requiredPackages,
29
+ setStyleImports,
28
30
  styleEntry,
29
31
  styleImportLines,
30
32
  TAILWIND_CSS,
@@ -146,51 +148,6 @@ function scaffold(
146
148
  'eslint.config.js': "import toiljs from 'toiljs/eslint';\n\nexport default toiljs;\n",
147
149
  '.prettierrc': '"toiljs/prettier"\n',
148
150
  '.gitignore': 'node_modules\nbuild\n.toil\ntoil-env.d.ts\n',
149
- 'client/public/index.html':
150
- '<!doctype html>\n<html lang="en">\n <head>\n' +
151
- ' <meta charset="utf-8" />\n' +
152
- ' <meta name="viewport" content="width=device-width, initial-scale=1" />\n' +
153
- ' <meta name="theme-color" content="#080D11" />\n' +
154
- ' <meta name="description" content="" />\n' +
155
- ' <link rel="icon" type="image/svg+xml" href="/favicon.svg" />\n' +
156
- ' <link rel="manifest" href="/manifest.webmanifest" />\n' +
157
- ` <title>${path.basename(name)}</title>\n` +
158
- ' </head>\n <body>\n <div id="root"></div>\n </body>\n</html>\n',
159
- 'client/public/favicon.svg':
160
- '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">\n' +
161
- ' <defs>\n' +
162
- ' <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">\n' +
163
- ' <stop offset="0" stop-color="#2563FF" />\n' +
164
- ' <stop offset="0.5" stop-color="#7C3AED" />\n' +
165
- ' <stop offset="1" stop-color="#22E3AB" />\n' +
166
- ' </linearGradient>\n' +
167
- ' </defs>\n' +
168
- ' <rect width="32" height="32" rx="7" fill="#080D11" />\n' +
169
- ' <path d="M9 10h14v3.2h-5.4V24h-3.2V13.2H9z" fill="url(#g)" />\n' +
170
- '</svg>\n',
171
- 'client/public/robots.txt': 'User-agent: *\nAllow: /\n',
172
- 'client/public/manifest.webmanifest':
173
- JSON.stringify(
174
- {
175
- name: path.basename(name),
176
- short_name: path.basename(name),
177
- start_url: '/',
178
- display: 'standalone',
179
- background_color: '#080D11',
180
- theme_color: '#080D11',
181
- icons: [{ src: '/favicon.svg', type: 'image/svg+xml', sizes: 'any' }],
182
- },
183
- null,
184
- 4,
185
- ) + '\n',
186
- 'client/public/images/.gitkeep': '# Place images and other static assets here; served at /images/*.\n',
187
- 'client/toil.tsx':
188
- "import { routes, layout, notFound } from 'toiljs/routes';\n\n" +
189
- styleImportLines(features).join('\n') +
190
- '\n\n' +
191
- 'Toil.mount(routes, layout, notFound);\n',
192
- [`client/${styleEntry(features.preprocessor)}`]: DEFAULT_STYLE_CONTENT,
193
- 'client/components/.gitkeep': '# Place shared React components here.\n',
194
151
  'toil-env.d.ts': TOIL_ENV_DTS,
195
152
  'toilconfig.json':
196
153
  JSON.stringify(
@@ -241,6 +198,70 @@ function scaffold(
241
198
  "import { add } from './index';\n\n" +
242
199
  '@main\nfunction run(): i32 {\n return add(40, 2);\n}\n',
243
200
  'README.md': ['# ' + path.basename(name), '', 'A [toiljs](https://toil.org) app.', '', '## Develop', '', ' npm install', ' npm run dev', '', '## Build', '', ' npm run build', ''].join('\n'),
201
+ };
202
+
203
+ // The `app` template's client UI is copied from templates/app at runtime; `minimal` ships an
204
+ // inline client here.
205
+ if (template === 'minimal') Object.assign(files, minimalClient(name, features));
206
+
207
+ // Selected AI-assistant pointer files at the root (committed). The real docs are always seeded
208
+ // under .toil/docs (gitignored; regenerated by dev/build) since the framework manages them.
209
+ Object.assign(files, aiHelperFiles(aiTools));
210
+ for (const [docName, content] of Object.entries(TOIL_DOCS)) {
211
+ files[`.toil/docs/${docName}`] = content;
212
+ }
213
+
214
+ return files;
215
+ }
216
+
217
+ /** The inline client UI for the `minimal` template (the `app` template copies templates/app). */
218
+ function minimalClient(name: string, features: StyleFeatures): Record<string, string> {
219
+ const files: Record<string, string> = {
220
+ 'client/public/index.html':
221
+ '<!doctype html>\n<html lang="en">\n <head>\n' +
222
+ ' <meta charset="utf-8" />\n' +
223
+ ' <meta name="viewport" content="width=device-width, initial-scale=1" />\n' +
224
+ ' <meta name="theme-color" content="#080D11" />\n' +
225
+ ' <meta name="description" content="" />\n' +
226
+ ' <link rel="icon" type="image/svg+xml" href="/favicon.svg" />\n' +
227
+ ' <link rel="manifest" href="/manifest.webmanifest" />\n' +
228
+ ` <title>${path.basename(name)}</title>\n` +
229
+ ' </head>\n <body>\n <div id="root"></div>\n </body>\n</html>\n',
230
+ 'client/public/favicon.svg':
231
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">\n' +
232
+ ' <defs>\n' +
233
+ ' <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">\n' +
234
+ ' <stop offset="0" stop-color="#2563FF" />\n' +
235
+ ' <stop offset="0.5" stop-color="#7C3AED" />\n' +
236
+ ' <stop offset="1" stop-color="#22E3AB" />\n' +
237
+ ' </linearGradient>\n' +
238
+ ' </defs>\n' +
239
+ ' <rect width="32" height="32" rx="7" fill="#080D11" />\n' +
240
+ ' <path d="M9 10h14v3.2h-5.4V24h-3.2V13.2H9z" fill="url(#g)" />\n' +
241
+ '</svg>\n',
242
+ 'client/public/robots.txt': 'User-agent: *\nAllow: /\n',
243
+ 'client/public/manifest.webmanifest':
244
+ JSON.stringify(
245
+ {
246
+ name: path.basename(name),
247
+ short_name: path.basename(name),
248
+ start_url: '/',
249
+ display: 'standalone',
250
+ background_color: '#080D11',
251
+ theme_color: '#080D11',
252
+ icons: [{ src: '/favicon.svg', type: 'image/svg+xml', sizes: 'any' }],
253
+ },
254
+ null,
255
+ 4,
256
+ ) + '\n',
257
+ 'client/public/images/.gitkeep': '# Place images and other static assets here; served at /images/*.\n',
258
+ 'client/toil.tsx':
259
+ "import { routes, layout, notFound } from 'toiljs/routes';\n\n" +
260
+ styleImportLines(features).join('\n') +
261
+ '\n\n' +
262
+ 'Toil.mount(routes, layout, notFound);\n',
263
+ [`client/${styleEntry(features.preprocessor)}`]: DEFAULT_STYLE_CONTENT,
264
+ 'client/components/.gitkeep': '# Place shared React components here.\n',
244
265
  'client/layout.tsx': `import { type ReactNode } from 'react';
245
266
 
246
267
  export default function Layout({ children }: { children?: ReactNode }) {
@@ -257,7 +278,7 @@ export default function Layout({ children }: { children?: ReactNode }) {
257
278
  }}>
258
279
  <strong style={{ color: '#2563FF', fontSize: '1.1rem' }}>${path.basename(name)}</strong>
259
280
  <nav style={{ display: 'flex', gap: '1rem' }}>
260
- <Toil.Link href="/">home</Toil.Link>${template === 'app' ? '\n <Toil.Link href="/about">about</Toil.Link>' : ''}
281
+ <Toil.Link href="/">home</Toil.Link>
261
282
  </nav>
262
283
  </header>
263
284
  {children}
@@ -270,38 +291,33 @@ export default function Layout({ children }: { children?: ReactNode }) {
270
291
  ' return (\n <main>\n' +
271
292
  ' <h1>Welcome to toiljs</h1>\n' +
272
293
  ' <p>File-based routing, bundled by Vite, zero config.</p>\n' +
273
- (template === 'app'
274
- ? ' <p>\n <Toil.Link href="/about">About</Toil.Link> · <Toil.Link href="/blog/42">Blog post 42</Toil.Link>\n </p>\n'
275
- : '') +
276
294
  ' </main>\n );\n}\n',
277
295
  };
296
+ if (features.tailwind) files[`client/${TAILWIND_ENTRY}`] = TAILWIND_CSS;
297
+ return files;
298
+ }
278
299
 
279
- if (template === 'app') {
280
- files['client/routes/about.tsx'] =
281
- 'export default function About() {\n' +
282
- ' return (\n <main>\n <h1>About</h1>\n' +
283
- ' <p>\n This page is served by <code>client/routes/about.tsx</code>.\n </p>\n' +
284
- ' <Toil.Link href="/">Back home</Toil.Link>\n </main>\n );\n}\n';
285
- files['client/routes/blog/[id].tsx'] =
286
- 'export default function BlogPost() {\n' +
287
- ' const { id } = Toil.useParams();\n' +
288
- ' return (\n <main>\n <h1>Blog post {id}</h1>\n' +
289
- ' <p>\n Dynamic route from <code>client/routes/blog/[id].tsx</code>.\n </p>\n' +
290
- ' <Toil.Link href="/">Back home</Toil.Link>\n </main>\n );\n}\n';
291
- }
300
+ /** Absolute path to a shipped template directory (e.g. `templates/app`). */
301
+ function templateDir(template: Template): string {
302
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'templates', template);
303
+ }
292
304
 
293
- if (features.tailwind) {
294
- files[`client/${TAILWIND_ENTRY}`] = TAILWIND_CSS;
305
+ /**
306
+ * Applies the chosen styling to a copied template's client dir: renames the stylesheet to the
307
+ * preprocessor's extension, adds the Tailwind entry, and rewrites `toil.tsx`'s style imports.
308
+ */
309
+ async function applyStyling(clientDir: string, features: StyleFeatures): Promise<void> {
310
+ // Plain CSS without Tailwind is exactly what the template ships — leave it byte-for-byte.
311
+ if (features.preprocessor === 'css' && !features.tailwind) return;
312
+ const entry = styleEntry(features.preprocessor);
313
+ if (entry !== 'styles/main.css') {
314
+ await fs.rename(path.join(clientDir, 'styles', 'main.css'), path.join(clientDir, entry));
295
315
  }
296
-
297
- // Selected AI-assistant pointer files at the root (committed). The real docs are always seeded
298
- // under .toil/docs (gitignored; regenerated by dev/build) since the framework manages them.
299
- Object.assign(files, aiHelperFiles(aiTools));
300
- for (const [name, content] of Object.entries(TOIL_DOCS)) {
301
- files[`.toil/docs/${name}`] = content;
316
+ if (features.tailwind) {
317
+ await fs.writeFile(path.join(clientDir, TAILWIND_ENTRY), TAILWIND_CSS, 'utf8');
302
318
  }
303
-
304
- return files;
319
+ const toilPath = path.join(clientDir, 'toil.tsx');
320
+ await fs.writeFile(toilPath, setStyleImports(await fs.readFile(toilPath, 'utf8'), features), 'utf8');
305
321
  }
306
322
 
307
323
  async function writeFiles(dir: string, files: Record<string, string>): Promise<void> {
@@ -366,7 +382,7 @@ export async function runCreate(opts: CreateOptions): Promise<void> {
366
382
  let template: Template = opts.template ?? 'app';
367
383
  if (!opts.template && !opts.yes) {
368
384
  const templateOptions: TemplateOption[] = [
369
- { value: 'app', label: 'App', hint: 'layout + home/about + a dynamic /blog/[id] route' },
385
+ { value: 'app', label: 'App', hint: 'the full ToilJS starter landing page, layout, styles, demo routes' },
370
386
  { value: 'minimal', label: 'Minimal', hint: 'just a layout and a home route' },
371
387
  ];
372
388
  const choice = await select({ message: 'Which template?', options: templateOptions, initialValue: 'app' });
@@ -430,6 +446,17 @@ export async function runCreate(opts: CreateOptions): Promise<void> {
430
446
  const s = spinner();
431
447
  s.start('Scaffolding project');
432
448
  await writeFiles(targetDir, scaffold(name, template, features, aiTools));
449
+ if (template === 'app') {
450
+ // Copy the full ToilJS starter UI, set its <title> to the project name, then apply styling.
451
+ await fs.cp(templateDir('app'), targetDir, { recursive: true });
452
+ const indexHtml = path.join(targetDir, 'client', 'public', 'index.html');
453
+ const html = await fs.readFile(indexHtml, 'utf8');
454
+ await fs.writeFile(
455
+ indexHtml,
456
+ html.replace(/<title>[^<]*<\/title>/, `<title>${path.basename(name)}</title>`),
457
+ );
458
+ await applyStyling(path.join(targetDir, 'client'), features);
459
+ }
433
460
  s.stop(`Scaffolded ${pc.cyan(rel)}`);
434
461
 
435
462
  if (initGit) {
@@ -8,14 +8,14 @@
8
8
  * `mount`, `match` (pure matcher), `prefetch` (link prefetcher), and `channel` (WebSocket helper).
9
9
  */
10
10
 
11
- export { mount } from './mount.js';
12
- export { Router } from './Router.js';
13
- export { Link } from './Link.js';
14
- export type { LinkProps } from './Link.js';
15
- export { NavLink, matchActive } from './NavLink.js';
16
- export type { NavLinkProps, NavLinkState } from './NavLink.js';
17
- export { navigate, back, forward, refresh } from './navigation.js';
18
- export type { NavigateOptions } from './navigation.js';
11
+ export { mount } from './routing/mount.js';
12
+ export { Router } from './routing/Router.js';
13
+ export { Link } from './navigation/Link.js';
14
+ export type { LinkProps } from './navigation/Link.js';
15
+ export { NavLink, matchActive } from './navigation/NavLink.js';
16
+ export type { NavLinkProps, NavLinkState } from './navigation/NavLink.js';
17
+ export { navigate, back, forward, refresh } from './navigation/navigation.js';
18
+ export type { NavigateOptions } from './navigation/navigation.js';
19
19
  export {
20
20
  useParams,
21
21
  useNavigate,
@@ -24,9 +24,9 @@ export {
24
24
  useSearchParams,
25
25
  useRouter,
26
26
  useNavigationPending,
27
- } from './hooks.js';
28
- export type { RouterInstance } from './hooks.js';
29
- export { prefetch } from './prefetch.js';
27
+ } from './routing/hooks.js';
28
+ export type { RouterInstance } from './routing/hooks.js';
29
+ export { prefetch } from './navigation/prefetch.js';
30
30
  export type {
31
31
  RouteDef,
32
32
  LayoutLoader,
@@ -34,9 +34,9 @@ export type {
34
34
  NotFoundLoader,
35
35
  RouteErrorProps,
36
36
  } from './types.js';
37
- export { matchRoute } from './match.js';
38
- export type { RouteParams } from './match.js';
39
- export { connectChannel, useChannel, resolveChannelUrl } from './channel.js';
40
- export type { Channel, ChannelOptions, ChannelHook, ChannelData } from './channel.js';
41
- export { useHead, useTitle, Head, mergeHead } from './head.js';
42
- export type { HeadSpec, MetaTag, LinkTag, ResolvedHead } from './head.js';
37
+ export { matchRoute } from './routing/match.js';
38
+ export type { RouteParams } from './routing/match.js';
39
+ export { connectChannel, useChannel, resolveChannelUrl } from './channel/channel.js';
40
+ export type { Channel, ChannelOptions, ChannelHook, ChannelData } from './channel/channel.js';
41
+ export { useHead, useTitle, Head, mergeHead } from './head/head.js';
42
+ export type { HeadSpec, MetaTag, LinkTag, ResolvedHead } from './head/head.js';
@@ -1,6 +1,6 @@
1
1
  import type { CSSProperties, ReactNode } from 'react';
2
2
 
3
- import { useLocation } from './hooks.js';
3
+ import { useLocation } from '../routing/hooks.js';
4
4
  import { Link, type LinkProps } from './Link.js';
5
5
 
6
6
  /** State passed to `NavLink`'s function-form `className` / `style` / `children`. */
@@ -1,5 +1,5 @@
1
- import { matchRoute } from './match.js';
2
- import type { RouteDef } from './types.js';
1
+ import { matchRoute } from '../routing/match.js';
2
+ import type { RouteDef } from '../types.js';
3
3
 
4
4
  declare global {
5
5
  interface Navigator {
@@ -12,9 +12,9 @@ import {
12
12
  } from './lazy.js';
13
13
  import { matchRoute, type RouteParams } from './match.js';
14
14
  import { ParamsContext } from './params-context.js';
15
- import { settleNavigation } from './navigation.js';
16
- import { applyScroll } from './scroll.js';
17
- import type { LayoutLoader, NotFoundLoader, RouteDef } from './types.js';
15
+ import { settleNavigation } from '../navigation/navigation.js';
16
+ import { applyScroll } from '../navigation/scroll.js';
17
+ import type { LayoutLoader, NotFoundLoader, RouteDef } from '../types.js';
18
18
 
19
19
  /** Matches the current location to a route and renders it, optionally wrapped in the root layout. */
20
20
  export function Router(props: {
@@ -1,6 +1,6 @@
1
1
  import { Component, Suspense, type ComponentType, type ReactNode } from 'react';
2
2
 
3
- import type { RouteErrorProps } from './types.js';
3
+ import type { RouteErrorProps } from '../types.js';
4
4
 
5
5
  interface ErrorBoundaryProps {
6
6
  readonly fallback: ComponentType<RouteErrorProps>;
@@ -21,9 +21,9 @@ import {
21
21
  subscribeLocation,
22
22
  subscribePending,
23
23
  type NavigateOptions,
24
- } from './navigation.js';
24
+ } from '../navigation/navigation.js';
25
25
  import { ParamsContext } from './params-context.js';
26
- import { prefetch } from './prefetch.js';
26
+ import { prefetch } from '../navigation/prefetch.js';
27
27
 
28
28
  /** Imperative router handle returned by {@link useRouter}. */
29
29
  export interface RouterInstance {
@@ -11,7 +11,7 @@ import type {
11
11
  NotFoundLoader,
12
12
  RouteDef,
13
13
  RouteErrorProps,
14
- } from './types.js';
14
+ } from '../types.js';
15
15
 
16
16
  type Loader<P> = () => Promise<{ default: ComponentType<P> }>;
17
17
 
@@ -1,9 +1,9 @@
1
1
  import { createRoot } from 'react-dom/client';
2
2
 
3
- import { initNavigation } from './navigation.js';
4
- import { startPrefetcher } from './prefetch.js';
3
+ import { initNavigation } from '../navigation/navigation.js';
4
+ import { startPrefetcher } from '../navigation/prefetch.js';
5
5
  import { Router } from './Router.js';
6
- import type { LayoutLoader, NotFoundLoader, RouteDef } from './types.js';
6
+ import type { LayoutLoader, NotFoundLoader, RouteDef } from '../types.js';
7
7
 
8
8
  /**
9
9
  * Mounts the toil client app into `#root` and starts idle link prefetching. Called by the
@@ -78,14 +78,23 @@ export function defineConfig(config: ToilConfig): ToilConfig {
78
78
  return config;
79
79
  }
80
80
 
81
- const CONFIG_NAMES = ['toil.config.ts', 'toil.config.mts', 'toil.config.js', 'toil.config.mjs'];
81
+ const CONFIG_NAMES = [
82
+ 'toil.config.ts',
83
+ 'toil.config.mts',
84
+ 'toil.config.js',
85
+ 'toil.config.mjs',
86
+ 'toiljs.config.ts',
87
+ 'toiljs.config.mts',
88
+ 'toiljs.config.js',
89
+ 'toiljs.config.mjs',
90
+ ];
82
91
 
83
92
  /** Path to the built client runtime (`build/client/index.js`), sibling to `build/compiler`. */
84
93
  function resolveRuntimePath(): string {
85
94
  return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../client/index.js');
86
95
  }
87
96
 
88
- /** Finds and loads `toil.config.*` from `root` (via Vite's bundling loader), then resolves defaults. */
97
+ /** Finds and loads `toil.config.*` or `toiljs.config.*` from `root`, then resolves defaults. */
89
98
  export async function loadConfig(
90
99
  opts: { root?: string; port?: number } = {},
91
100
  ): Promise<ResolvedToilConfig> {
@@ -13,6 +13,27 @@ import { scanRoutes, type ScannedRoute } from './routes.js';
13
13
  * Lives at the project root because TypeScript's `include` globs skip dot-directories.
14
14
  * Exported so `toiljs create` can write it during scaffolding, before the first dev/build.
15
15
  */
16
+ /** Side-effect style imports (e.g. `import './styles/main.css'`). */
17
+ const STYLE_EXTENSIONS = ['css', 'scss', 'sass', 'less', 'styl', 'stylus', 'pcss', 'sss'];
18
+ /** Asset imports whose default export is the resolved URL string (e.g. `import logo from './logo.svg'`). */
19
+ const ASSET_EXTENSIONS = [
20
+ 'svg',
21
+ 'png',
22
+ 'jpg',
23
+ 'jpeg',
24
+ 'gif',
25
+ 'webp',
26
+ 'avif',
27
+ 'ico',
28
+ 'bmp',
29
+ 'apng',
30
+ ];
31
+
32
+ const STYLE_MODULES = STYLE_EXTENSIONS.map((ext) => `declare module '*.${ext}' {}`).join('\n');
33
+ const ASSET_MODULES = ASSET_EXTENSIONS.map(
34
+ (ext) => `declare module '*.${ext}' {\n const src: string;\n export default src;\n}`,
35
+ ).join('\n');
36
+
16
37
  export const TOIL_ENV_DTS =
17
38
  `// AUTO-GENERATED by toil — do not edit.\n` +
18
39
  `declare const Toil: typeof import('toiljs/client');\n` +
@@ -21,14 +42,9 @@ export const TOIL_ENV_DTS =
21
42
  `declare const FastMap: typeof import('toiljs/io').FastMap;\n` +
22
43
  `declare const FastSet: typeof import('toiljs/io').FastSet;\n` +
23
44
  `\n` +
24
- `declare module '*.css' {}\n` +
25
- `declare module '*.scss' {}\n` +
26
- `declare module '*.sass' {}\n` +
27
- `declare module '*.less' {}\n` +
28
- `declare module '*.styl' {}\n` +
29
- `declare module '*.stylus' {}\n` +
30
- `declare module '*.pcss' {}\n` +
31
- `declare module '*.sss' {}\n` +
45
+ `${STYLE_MODULES}\n` +
46
+ `\n` +
47
+ `${ASSET_MODULES}\n` +
32
48
  `\n` +
33
49
  `declare module 'toiljs/routes' {\n` +
34
50
  ` export const routes: import('toiljs/client').RouteDef[];\n` +
@@ -11,6 +11,25 @@ import { generate } from './generate.js';
11
11
  export function toilPlugin(cfg: ResolvedToilConfig): Plugin {
12
12
  return {
13
13
  name: 'toil',
14
+ // Catch empty import specifiers in source and report the file — rolldown otherwise fails
15
+ // resolution with a cryptic "The specifiers must be a non-empty string. Received ''".
16
+ transform(code, id) {
17
+ const file = id.split('?')[0];
18
+ if (id.includes('\0') || file.includes('/node_modules/') || !/\.[mc]?[jt]sx?$/.test(file)) {
19
+ return null;
20
+ }
21
+ const empty =
22
+ /^[ \t]*import\s+(['"])\1\s*;?[ \t]*$/m.test(code) ||
23
+ /^[ \t]*import\b[^'"\n]*\bfrom\s+(['"])\1/m.test(code) ||
24
+ /^[ \t]*export\b[^'"\n]*\bfrom\s+(['"])\1/m.test(code) ||
25
+ /\bimport\s*\(\s*(['"])\1\s*\)/.test(code);
26
+ if (empty) {
27
+ throw new Error(
28
+ `toil: empty import specifier (e.g. \`import '';\`) in ${file} — remove or complete the import.`,
29
+ );
30
+ }
31
+ return null;
32
+ },
14
33
  configureServer(server) {
15
34
  // Trailing slash so a sibling like `routes-extra/` doesn't match the `routes/` prefix.
16
35
  const routesPrefix = cfg.routesAbsDir.replace(/\\/g, '/').replace(/\/?$/, '/');
@@ -0,0 +1,11 @@
1
+ export default function NotFound() {
2
+ return (
3
+ <main>
4
+ <h1>404 — Page not found</h1>
5
+ <p>
6
+ This custom page is served from <code>client/404.tsx</code> whenever no route matches.
7
+ </p>
8
+ <Toil.Link href="/">Back home</Toil.Link>
9
+ </main>
10
+ );
11
+ }
@@ -0,0 +1 @@
1
+ # Place shared React components here.
@@ -0,0 +1,8 @@
1
+ export default function Footer() {
2
+ return (
3
+ <footer className="footer">
4
+ Built with ToilJS · Powered by Dacely
5
+ </footer>
6
+ );
7
+ }
8
+