toiljs 0.0.11 → 0.0.12
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 +2 -0
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/configure.js +10 -4
- package/build/cli/create.js +58 -30
- package/build/cli/diagnostics.d.ts +55 -0
- package/build/cli/diagnostics.js +333 -0
- package/build/cli/doctor.d.ts +6 -0
- package/build/cli/doctor.js +249 -0
- package/build/cli/index.js +26 -0
- package/build/cli/proc.d.ts +5 -0
- package/build/cli/proc.js +20 -0
- package/build/cli/ui.d.ts +1 -0
- package/build/cli/ui.js +1 -0
- package/build/cli/update.d.ts +7 -0
- package/build/cli/update.js +117 -0
- package/build/cli/updates.d.ts +10 -0
- package/build/cli/updates.js +45 -0
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/dev/error-overlay.js +1 -1
- package/build/client/head/metadata.js +3 -1
- package/build/client/index.d.ts +5 -1
- package/build/client/index.js +2 -0
- package/build/client/navigation/navigation.js +1 -1
- package/build/client/routing/Router.js +2 -2
- package/build/client/search/search.d.ts +26 -0
- package/build/client/search/search.js +101 -0
- package/build/client/search/use-page-search.d.ts +8 -0
- package/build/client/search/use-page-search.js +21 -0
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/generate.js +26 -23
- package/build/compiler/index.d.ts +2 -0
- package/build/compiler/index.js +1 -0
- package/build/compiler/pages.d.ts +8 -0
- package/build/compiler/pages.js +37 -0
- package/build/compiler/plugin.js +3 -1
- package/build/compiler/prerender.d.ts +1 -0
- package/build/compiler/prerender.js +11 -5
- package/build/compiler/seo.js +10 -3
- package/build/io/.tsbuildinfo +1 -1
- package/examples/basic/client/components/Header.tsx +43 -41
- package/examples/basic/client/components/HoneycombBackground.tsx +223 -230
- package/examples/basic/client/public/index.html +18 -16
- package/examples/basic/client/routes/(legal)/privacy.tsx +18 -19
- package/examples/basic/client/routes/(legal)/terms.tsx +15 -16
- package/examples/basic/client/routes/about.tsx +21 -22
- package/examples/basic/client/routes/blog/[id].tsx +26 -18
- package/examples/basic/client/routes/features/actions.tsx +67 -67
- package/examples/basic/client/routes/features/error/index.tsx +27 -27
- package/examples/basic/client/routes/features/head.tsx +38 -38
- package/examples/basic/client/routes/features/index.tsx +83 -75
- package/examples/basic/client/routes/features/realtime.tsx +34 -32
- package/examples/basic/client/routes/features/script.tsx +31 -31
- package/examples/basic/client/routes/features/seo.tsx +39 -39
- package/examples/basic/client/routes/features/template/index.tsx +20 -20
- package/examples/basic/client/routes/features/template/template.tsx +16 -18
- package/examples/basic/client/routes/gallery/@modal/(.)photo/[id].tsx +23 -23
- package/examples/basic/client/routes/gallery/index.tsx +42 -42
- package/examples/basic/client/routes/gallery/photo/[id].tsx +18 -18
- package/examples/basic/client/routes/get-started.tsx +157 -84
- package/examples/basic/client/routes/index.tsx +137 -96
- package/examples/basic/client/routes/loader-demo/index.tsx +59 -52
- package/examples/basic/client/routes/search.tsx +61 -0
- package/examples/basic/client/routes/test.tsx +7 -8
- package/examples/basic/client/styles/main.css +624 -552
- package/package.json +2 -2
- package/presets/eslint.js +10 -3
- package/src/cli/configure.ts +363 -353
- package/src/cli/create.ts +563 -530
- package/src/cli/diagnostics.ts +421 -0
- package/src/cli/doctor.ts +318 -0
- package/src/cli/features.ts +166 -160
- package/src/cli/index.ts +242 -211
- package/src/cli/proc.ts +30 -0
- package/src/cli/ui.ts +111 -103
- package/src/cli/update.ts +150 -0
- package/src/cli/updates.ts +69 -0
- package/src/client/components/Image.tsx +91 -89
- package/src/client/dev/error-overlay.tsx +193 -197
- package/src/client/head/metadata.ts +94 -92
- package/src/client/index.ts +79 -64
- package/src/client/navigation/Link.tsx +94 -100
- package/src/client/navigation/navigation.ts +215 -218
- package/src/client/routing/Router.tsx +210 -193
- package/src/client/routing/hooks.ts +110 -114
- package/src/client/routing/lazy.ts +77 -81
- package/src/client/search/search.ts +189 -0
- package/src/client/search/use-page-search.ts +73 -0
- package/src/compiler/config.ts +173 -171
- package/src/compiler/fonts.ts +89 -87
- package/src/compiler/generate.ts +378 -373
- package/src/compiler/image-report.ts +88 -85
- package/src/compiler/index.ts +2 -0
- package/src/compiler/pages.ts +70 -0
- package/src/compiler/plugin.ts +51 -47
- package/src/compiler/prerender.ts +152 -130
- package/src/compiler/routes.ts +132 -131
- package/src/compiler/seo.ts +381 -356
- package/src/compiler/vite.ts +155 -145
- package/src/io/FastSet.ts +99 -96
- package/test/configure.test.ts +94 -90
- package/test/doctor.test.ts +140 -0
- package/test/dom/Image.test.tsx +73 -46
- package/test/dom/Script.test.tsx +48 -45
- package/test/dom/action.test.tsx +146 -129
- package/test/dom/error-overlay.test.tsx +44 -44
- package/test/dom/loader.test.tsx +2 -2
- package/test/dom/revalidate.test.tsx +1 -1
- package/test/dom/route-head.test.tsx +1 -2
- package/test/dom/slot.test.tsx +131 -109
- package/test/dom/view-transitions.test.tsx +53 -51
- package/test/features.test.ts +149 -142
- package/test/fonts.test.ts +28 -26
- package/test/head.test.ts +45 -35
- package/test/metadata.test.ts +42 -41
- package/test/pages.test.ts +105 -0
- package/test/prerender.test.ts +54 -46
- package/test/search.test.ts +114 -0
- package/test/seo.test.ts +164 -142
- package/test/update.test.ts +44 -0
package/src/compiler/config.ts
CHANGED
|
@@ -1,171 +1,173 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
|
-
|
|
5
|
-
import { type InlineConfig } from 'vite';
|
|
6
|
-
|
|
7
|
-
import { type SeoConfig } from './seo.js';
|
|
8
|
-
|
|
9
|
-
export type { SeoConfig } from './seo.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Client-side (TSX/React/Vite) configuration. All fields optional; sensible defaults applied.
|
|
13
|
-
*/
|
|
14
|
-
export interface ClientConfig {
|
|
15
|
-
/** Client source directory, relative to root. Default `client`. */
|
|
16
|
-
readonly srcDir?: string;
|
|
17
|
-
/** Routes directory, relative to `srcDir`. Default `routes`. */
|
|
18
|
-
readonly routesDir?: string;
|
|
19
|
-
/**
|
|
20
|
-
* Static assets directory, relative to root. Default `<srcDir>/public` (e.g. `client/public`).
|
|
21
|
-
* Holds the `index.html` template (owned and edited by you) plus any files served as-is at the
|
|
22
|
-
* base path (favicons, images, …).
|
|
23
|
-
*/
|
|
24
|
-
readonly publicDir?: string;
|
|
25
|
-
/** Production output directory, relative to root. Default `build/client`. */
|
|
26
|
-
readonly outDir?: string;
|
|
27
|
-
/** Public base path. Default `/`. */
|
|
28
|
-
readonly base?: string;
|
|
29
|
-
/** Dev server port. Default `3000`. */
|
|
30
|
-
readonly port?: number;
|
|
31
|
-
/**
|
|
32
|
-
* Optimize imported images at build time (resize/convert via `vite-imagetools` + sharp): an
|
|
33
|
-
* import like `logo.png?w=400;800&format=webp&as=srcset` emits resized, compressed variants.
|
|
34
|
-
* Default `true`. Set `false` to disable the pipeline (images are then served as-is).
|
|
35
|
-
*/
|
|
36
|
-
readonly images?: boolean;
|
|
37
|
-
/**
|
|
38
|
-
* Preload bundled fonts at build time: injects `<link rel="preload" as="font">` for each
|
|
39
|
-
* `@font-face` font so it loads in parallel with the CSS (faster text paint). Default `true`.
|
|
40
|
-
*/
|
|
41
|
-
readonly fonts?: boolean;
|
|
42
|
-
/**
|
|
43
|
-
* Animate cross-page navigations with the browser View Transitions API (a crossfade by default;
|
|
44
|
-
* add `view-transition-name` in CSS for shared-element transitions). Respects
|
|
45
|
-
* `prefers-reduced-motion`. Default `false`.
|
|
46
|
-
*/
|
|
47
|
-
readonly viewTransitions?: boolean;
|
|
48
|
-
/**
|
|
49
|
-
* Build-time SEO: bakes site-level metadata into the HTML `<head>` (so JS-less crawlers and AI
|
|
50
|
-
* bots see real tags) and generates `robots.txt`, `sitemap.xml`, and `llms.txt`. Omit to skip.
|
|
51
|
-
*/
|
|
52
|
-
readonly seo?: SeoConfig;
|
|
53
|
-
/**
|
|
54
|
-
* Raw Vite escape hatch, deep-merged over the framework's opinionated config.
|
|
55
|
-
* This is NOT the client config itself, toil owns the Vite setup; use this only
|
|
56
|
-
* to override specific Vite options.
|
|
57
|
-
*/
|
|
58
|
-
readonly vite?: InlineConfig;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Server-side (toilscript → WASM) configuration. Reserved: the compiler does not yet
|
|
63
|
-
* build the server target via `toil build`; today it is compiled by `toilscript` directly.
|
|
64
|
-
*/
|
|
65
|
-
export interface ServerConfig {
|
|
66
|
-
/** Server source directory, relative to root. Default `server`. */
|
|
67
|
-
readonly srcDir?: string;
|
|
68
|
-
/** Server build output directory, relative to root. Default `build/server`. */
|
|
69
|
-
readonly outDir?: string;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* The `toil.config` schema. All fields optional; sensible defaults applied.
|
|
74
|
-
* Client and server are configured in separate sections.
|
|
75
|
-
*/
|
|
76
|
-
export interface ToilConfig {
|
|
77
|
-
/** Project root. Defaults to the current working directory. */
|
|
78
|
-
readonly root?: string;
|
|
79
|
-
/** Client (TSX/React/Vite) configuration. */
|
|
80
|
-
readonly client?: ClientConfig;
|
|
81
|
-
/** Server (toilscript/WASM) configuration. */
|
|
82
|
-
readonly server?: ServerConfig;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Fully-resolved config with absolute paths, used internally by the compiler. */
|
|
86
|
-
export interface ResolvedToilConfig {
|
|
87
|
-
readonly root: string;
|
|
88
|
-
readonly srcDir: string;
|
|
89
|
-
readonly clientAbsDir: string;
|
|
90
|
-
readonly routesAbsDir: string;
|
|
91
|
-
/** Absolute path to the static-assets dir (holds the `index.html` template). */
|
|
92
|
-
readonly publicDir: string;
|
|
93
|
-
readonly toilDir: string;
|
|
94
|
-
readonly outDir: string;
|
|
95
|
-
readonly base: string;
|
|
96
|
-
readonly port: number;
|
|
97
|
-
/** Whether build-time image optimization (`vite-imagetools`) is enabled. */
|
|
98
|
-
readonly images: boolean;
|
|
99
|
-
/** Whether build-time font preloading is enabled. */
|
|
100
|
-
readonly fonts: boolean;
|
|
101
|
-
/** Whether animated View Transitions are enabled for navigation. */
|
|
102
|
-
readonly viewTransitions: boolean;
|
|
103
|
-
/** Build-time SEO config, or `null` when not configured. */
|
|
104
|
-
readonly seo: SeoConfig | null;
|
|
105
|
-
/** Absolute path to the framework client runtime (`toiljs/client`). */
|
|
106
|
-
readonly runtimePath: string;
|
|
107
|
-
readonly vite: InlineConfig;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** Identity helper for typed config files: `export default defineConfig({ ... })`. */
|
|
111
|
-
export function defineConfig(config: ToilConfig): ToilConfig {
|
|
112
|
-
return config;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const CONFIG_NAMES = [
|
|
116
|
-
'toil.config.ts',
|
|
117
|
-
'toil.config.mts',
|
|
118
|
-
'toil.config.js',
|
|
119
|
-
'toil.config.mjs',
|
|
120
|
-
'toiljs.config.ts',
|
|
121
|
-
'toiljs.config.mts',
|
|
122
|
-
'toiljs.config.js',
|
|
123
|
-
'toiljs.config.mjs',
|
|
124
|
-
];
|
|
125
|
-
|
|
126
|
-
/** Path to the built client runtime (`build/client/index.js`), sibling to `build/compiler`. */
|
|
127
|
-
function resolveRuntimePath(): string {
|
|
128
|
-
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../client/index.js');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/** Finds and loads `toil.config.*` or `toiljs.config.*` from `root`, then resolves defaults. */
|
|
132
|
-
export async function loadConfig(
|
|
133
|
-
opts: { root?: string; port?: number } = {},
|
|
134
|
-
): Promise<ResolvedToilConfig> {
|
|
135
|
-
const root = path.resolve(opts.root ?? process.cwd());
|
|
136
|
-
|
|
137
|
-
let user: ToilConfig = {};
|
|
138
|
-
for (const name of CONFIG_NAMES) {
|
|
139
|
-
const candidate = path.join(root, name);
|
|
140
|
-
if (fs.existsSync(candidate)) {
|
|
141
|
-
const loaded = (await import(pathToFileURL(candidate).href)) as {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { type InlineConfig } from 'vite';
|
|
6
|
+
|
|
7
|
+
import { type SeoConfig } from './seo.js';
|
|
8
|
+
|
|
9
|
+
export type { SeoConfig } from './seo.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Client-side (TSX/React/Vite) configuration. All fields optional; sensible defaults applied.
|
|
13
|
+
*/
|
|
14
|
+
export interface ClientConfig {
|
|
15
|
+
/** Client source directory, relative to root. Default `client`. */
|
|
16
|
+
readonly srcDir?: string;
|
|
17
|
+
/** Routes directory, relative to `srcDir`. Default `routes`. */
|
|
18
|
+
readonly routesDir?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Static assets directory, relative to root. Default `<srcDir>/public` (e.g. `client/public`).
|
|
21
|
+
* Holds the `index.html` template (owned and edited by you) plus any files served as-is at the
|
|
22
|
+
* base path (favicons, images, …).
|
|
23
|
+
*/
|
|
24
|
+
readonly publicDir?: string;
|
|
25
|
+
/** Production output directory, relative to root. Default `build/client`. */
|
|
26
|
+
readonly outDir?: string;
|
|
27
|
+
/** Public base path. Default `/`. */
|
|
28
|
+
readonly base?: string;
|
|
29
|
+
/** Dev server port. Default `3000`. */
|
|
30
|
+
readonly port?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Optimize imported images at build time (resize/convert via `vite-imagetools` + sharp): an
|
|
33
|
+
* import like `logo.png?w=400;800&format=webp&as=srcset` emits resized, compressed variants.
|
|
34
|
+
* Default `true`. Set `false` to disable the pipeline (images are then served as-is).
|
|
35
|
+
*/
|
|
36
|
+
readonly images?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Preload bundled fonts at build time: injects `<link rel="preload" as="font">` for each
|
|
39
|
+
* `@font-face` font so it loads in parallel with the CSS (faster text paint). Default `true`.
|
|
40
|
+
*/
|
|
41
|
+
readonly fonts?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Animate cross-page navigations with the browser View Transitions API (a crossfade by default;
|
|
44
|
+
* add `view-transition-name` in CSS for shared-element transitions). Respects
|
|
45
|
+
* `prefers-reduced-motion`. Default `false`.
|
|
46
|
+
*/
|
|
47
|
+
readonly viewTransitions?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Build-time SEO: bakes site-level metadata into the HTML `<head>` (so JS-less crawlers and AI
|
|
50
|
+
* bots see real tags) and generates `robots.txt`, `sitemap.xml`, and `llms.txt`. Omit to skip.
|
|
51
|
+
*/
|
|
52
|
+
readonly seo?: SeoConfig;
|
|
53
|
+
/**
|
|
54
|
+
* Raw Vite escape hatch, deep-merged over the framework's opinionated config.
|
|
55
|
+
* This is NOT the client config itself, toil owns the Vite setup; use this only
|
|
56
|
+
* to override specific Vite options.
|
|
57
|
+
*/
|
|
58
|
+
readonly vite?: InlineConfig;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Server-side (toilscript → WASM) configuration. Reserved: the compiler does not yet
|
|
63
|
+
* build the server target via `toil build`; today it is compiled by `toilscript` directly.
|
|
64
|
+
*/
|
|
65
|
+
export interface ServerConfig {
|
|
66
|
+
/** Server source directory, relative to root. Default `server`. */
|
|
67
|
+
readonly srcDir?: string;
|
|
68
|
+
/** Server build output directory, relative to root. Default `build/server`. */
|
|
69
|
+
readonly outDir?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The `toil.config` schema. All fields optional; sensible defaults applied.
|
|
74
|
+
* Client and server are configured in separate sections.
|
|
75
|
+
*/
|
|
76
|
+
export interface ToilConfig {
|
|
77
|
+
/** Project root. Defaults to the current working directory. */
|
|
78
|
+
readonly root?: string;
|
|
79
|
+
/** Client (TSX/React/Vite) configuration. */
|
|
80
|
+
readonly client?: ClientConfig;
|
|
81
|
+
/** Server (toilscript/WASM) configuration. */
|
|
82
|
+
readonly server?: ServerConfig;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Fully-resolved config with absolute paths, used internally by the compiler. */
|
|
86
|
+
export interface ResolvedToilConfig {
|
|
87
|
+
readonly root: string;
|
|
88
|
+
readonly srcDir: string;
|
|
89
|
+
readonly clientAbsDir: string;
|
|
90
|
+
readonly routesAbsDir: string;
|
|
91
|
+
/** Absolute path to the static-assets dir (holds the `index.html` template). */
|
|
92
|
+
readonly publicDir: string;
|
|
93
|
+
readonly toilDir: string;
|
|
94
|
+
readonly outDir: string;
|
|
95
|
+
readonly base: string;
|
|
96
|
+
readonly port: number;
|
|
97
|
+
/** Whether build-time image optimization (`vite-imagetools`) is enabled. */
|
|
98
|
+
readonly images: boolean;
|
|
99
|
+
/** Whether build-time font preloading is enabled. */
|
|
100
|
+
readonly fonts: boolean;
|
|
101
|
+
/** Whether animated View Transitions are enabled for navigation. */
|
|
102
|
+
readonly viewTransitions: boolean;
|
|
103
|
+
/** Build-time SEO config, or `null` when not configured. */
|
|
104
|
+
readonly seo: SeoConfig | null;
|
|
105
|
+
/** Absolute path to the framework client runtime (`toiljs/client`). */
|
|
106
|
+
readonly runtimePath: string;
|
|
107
|
+
readonly vite: InlineConfig;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Identity helper for typed config files: `export default defineConfig({ ... })`. */
|
|
111
|
+
export function defineConfig(config: ToilConfig): ToilConfig {
|
|
112
|
+
return config;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const CONFIG_NAMES = [
|
|
116
|
+
'toil.config.ts',
|
|
117
|
+
'toil.config.mts',
|
|
118
|
+
'toil.config.js',
|
|
119
|
+
'toil.config.mjs',
|
|
120
|
+
'toiljs.config.ts',
|
|
121
|
+
'toiljs.config.mts',
|
|
122
|
+
'toiljs.config.js',
|
|
123
|
+
'toiljs.config.mjs',
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
/** Path to the built client runtime (`build/client/index.js`), sibling to `build/compiler`. */
|
|
127
|
+
function resolveRuntimePath(): string {
|
|
128
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../client/index.js');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Finds and loads `toil.config.*` or `toiljs.config.*` from `root`, then resolves defaults. */
|
|
132
|
+
export async function loadConfig(
|
|
133
|
+
opts: { root?: string; port?: number } = {},
|
|
134
|
+
): Promise<ResolvedToilConfig> {
|
|
135
|
+
const root = path.resolve(opts.root ?? process.cwd());
|
|
136
|
+
|
|
137
|
+
let user: ToilConfig = {};
|
|
138
|
+
for (const name of CONFIG_NAMES) {
|
|
139
|
+
const candidate = path.join(root, name);
|
|
140
|
+
if (fs.existsSync(candidate)) {
|
|
141
|
+
const loaded = (await import(pathToFileURL(candidate).href)) as {
|
|
142
|
+
default?: ToilConfig;
|
|
143
|
+
};
|
|
144
|
+
if (loaded.default) user = loaded.default;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const client = user.client ?? {};
|
|
150
|
+
const srcDir = client.srcDir ?? 'client';
|
|
151
|
+
const routesDir = client.routesDir ?? 'routes';
|
|
152
|
+
const clientAbsDir = path.join(root, srcDir);
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
root,
|
|
156
|
+
srcDir,
|
|
157
|
+
clientAbsDir,
|
|
158
|
+
routesAbsDir: path.join(clientAbsDir, routesDir),
|
|
159
|
+
publicDir: client.publicDir
|
|
160
|
+
? path.resolve(root, client.publicDir)
|
|
161
|
+
: path.join(clientAbsDir, 'public'),
|
|
162
|
+
toilDir: path.join(root, '.toil'),
|
|
163
|
+
outDir: client.outDir ?? 'build/client',
|
|
164
|
+
base: client.base ?? '/',
|
|
165
|
+
port: opts.port ?? client.port ?? 3000,
|
|
166
|
+
images: client.images ?? true,
|
|
167
|
+
fonts: client.fonts ?? true,
|
|
168
|
+
viewTransitions: client.viewTransitions ?? false,
|
|
169
|
+
seo: client.seo ?? null,
|
|
170
|
+
runtimePath: resolveRuntimePath(),
|
|
171
|
+
vite: client.vite ?? {},
|
|
172
|
+
};
|
|
173
|
+
}
|
package/src/compiler/fonts.ts
CHANGED
|
@@ -1,87 +1,89 @@
|
|
|
1
|
-
import type { HtmlTagDescriptor, Logger, Plugin } from 'vite';
|
|
2
|
-
|
|
3
|
-
import { type ResolvedToilConfig } from './config.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Build-time font optimization. Bundled font files (`@font-face` `url(...)` imports) are emitted +
|
|
7
|
-
* hashed by Vite, but without a hint the browser only discovers them after parsing CSS, delaying
|
|
8
|
-
* text paint. This injects a `<link rel="preload" as="font" crossorigin>` for each bundled font so
|
|
9
|
-
* it loads in parallel with the CSS, and logs what it preloaded (mirrors the image-optimization log).
|
|
10
|
-
*/
|
|
11
|
-
const FONT_RE = /\.(woff2|woff|ttf|otf)$/i;
|
|
12
|
-
const FONT_TYPE: Record<string, string> = {
|
|
13
|
-
woff2: 'font/woff2',
|
|
14
|
-
woff: 'font/woff',
|
|
15
|
-
ttf: 'font/ttf',
|
|
16
|
-
otf: 'font/otf',
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
function kb(bytes: number): string {
|
|
20
|
-
return `${(bytes / 1000).toFixed(2)} kB`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Builds the `<link rel="preload">` head tags for a set of bundled font file names. */
|
|
24
|
-
export function fontPreloadTags(fileNames: readonly string[], base: string): HtmlTagDescriptor[] {
|
|
25
|
-
const prefix = base.endsWith('/') ? base : `${base}/`;
|
|
26
|
-
return fileNames
|
|
27
|
-
.filter((name) => FONT_RE.test(name))
|
|
28
|
-
.map((name) => {
|
|
29
|
-
const ext = name.split('.').pop()?.toLowerCase() ?? '';
|
|
30
|
-
return {
|
|
31
|
-
tag: 'link',
|
|
32
|
-
attrs: {
|
|
33
|
-
rel: 'preload',
|
|
34
|
-
as: 'font',
|
|
35
|
-
type: FONT_TYPE[ext] ?? `font/${ext}`,
|
|
36
|
-
href: `${prefix}${name}`,
|
|
37
|
-
crossorigin: '',
|
|
38
|
-
},
|
|
39
|
-
injectTo: 'head',
|
|
40
|
-
};
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Build-only plugin that preloads bundled fonts and logs them. Disabled by `client.fonts: false`. */
|
|
45
|
-
export function fontPreloadPlugin(cfg: ResolvedToilConfig): Plugin {
|
|
46
|
-
let logger: Logger | undefined;
|
|
47
|
-
let logged = false;
|
|
48
|
-
return {
|
|
49
|
-
name: 'toil:font-preload',
|
|
50
|
-
apply: 'build',
|
|
51
|
-
configResolved(config) {
|
|
52
|
-
logger = config.logger;
|
|
53
|
-
},
|
|
54
|
-
transformIndexHtml: {
|
|
55
|
-
order: 'post',
|
|
56
|
-
handler(html, ctx) {
|
|
57
|
-
const bundle = ctx.bundle ?? {};
|
|
58
|
-
const fonts = Object.values(bundle).filter(
|
|
59
|
-
(file) => file.type === 'asset' && FONT_RE.test(file.fileName),
|
|
60
|
-
);
|
|
61
|
-
if (fonts.length === 0) return html;
|
|
62
|
-
|
|
63
|
-
// Log once (the same template's HTML is transformed per emitted page).
|
|
64
|
-
if (!logged && logger) {
|
|
65
|
-
logged = true;
|
|
66
|
-
logger.info('');
|
|
67
|
-
logger.info(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
1
|
+
import type { HtmlTagDescriptor, Logger, Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
import { type ResolvedToilConfig } from './config.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Build-time font optimization. Bundled font files (`@font-face` `url(...)` imports) are emitted +
|
|
7
|
+
* hashed by Vite, but without a hint the browser only discovers them after parsing CSS, delaying
|
|
8
|
+
* text paint. This injects a `<link rel="preload" as="font" crossorigin>` for each bundled font so
|
|
9
|
+
* it loads in parallel with the CSS, and logs what it preloaded (mirrors the image-optimization log).
|
|
10
|
+
*/
|
|
11
|
+
const FONT_RE = /\.(woff2|woff|ttf|otf)$/i;
|
|
12
|
+
const FONT_TYPE: Record<string, string> = {
|
|
13
|
+
woff2: 'font/woff2',
|
|
14
|
+
woff: 'font/woff',
|
|
15
|
+
ttf: 'font/ttf',
|
|
16
|
+
otf: 'font/otf',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function kb(bytes: number): string {
|
|
20
|
+
return `${(bytes / 1000).toFixed(2)} kB`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Builds the `<link rel="preload">` head tags for a set of bundled font file names. */
|
|
24
|
+
export function fontPreloadTags(fileNames: readonly string[], base: string): HtmlTagDescriptor[] {
|
|
25
|
+
const prefix = base.endsWith('/') ? base : `${base}/`;
|
|
26
|
+
return fileNames
|
|
27
|
+
.filter((name) => FONT_RE.test(name))
|
|
28
|
+
.map((name) => {
|
|
29
|
+
const ext = name.split('.').pop()?.toLowerCase() ?? '';
|
|
30
|
+
return {
|
|
31
|
+
tag: 'link',
|
|
32
|
+
attrs: {
|
|
33
|
+
rel: 'preload',
|
|
34
|
+
as: 'font',
|
|
35
|
+
type: FONT_TYPE[ext] ?? `font/${ext}`,
|
|
36
|
+
href: `${prefix}${name}`,
|
|
37
|
+
crossorigin: '',
|
|
38
|
+
},
|
|
39
|
+
injectTo: 'head',
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Build-only plugin that preloads bundled fonts and logs them. Disabled by `client.fonts: false`. */
|
|
45
|
+
export function fontPreloadPlugin(cfg: ResolvedToilConfig): Plugin {
|
|
46
|
+
let logger: Logger | undefined;
|
|
47
|
+
let logged = false;
|
|
48
|
+
return {
|
|
49
|
+
name: 'toil:font-preload',
|
|
50
|
+
apply: 'build',
|
|
51
|
+
configResolved(config) {
|
|
52
|
+
logger = config.logger;
|
|
53
|
+
},
|
|
54
|
+
transformIndexHtml: {
|
|
55
|
+
order: 'post',
|
|
56
|
+
handler(html, ctx) {
|
|
57
|
+
const bundle = ctx.bundle ?? {};
|
|
58
|
+
const fonts = Object.values(bundle).filter(
|
|
59
|
+
(file) => file.type === 'asset' && FONT_RE.test(file.fileName),
|
|
60
|
+
);
|
|
61
|
+
if (fonts.length === 0) return html;
|
|
62
|
+
|
|
63
|
+
// Log once (the same template's HTML is transformed per emitted page).
|
|
64
|
+
if (!logged && logger) {
|
|
65
|
+
logged = true;
|
|
66
|
+
logger.info('');
|
|
67
|
+
logger.info(
|
|
68
|
+
` ✓ preloaded ${String(fonts.length)} font${fonts.length === 1 ? '' : 's'}`,
|
|
69
|
+
);
|
|
70
|
+
for (const file of fonts) {
|
|
71
|
+
const size =
|
|
72
|
+
file.type === 'asset' && typeof file.source !== 'string'
|
|
73
|
+
? kb(file.source.byteLength)
|
|
74
|
+
: '';
|
|
75
|
+
logger.info(` → ${file.fileName} ${size}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
html,
|
|
81
|
+
tags: fontPreloadTags(
|
|
82
|
+
fonts.map((f) => f.fileName),
|
|
83
|
+
cfg.base,
|
|
84
|
+
),
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|