@uniflowed/vite 0.0.0-alpha.9 → 0.2.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/driver.js +1855 -190
- package/index.js +1013 -124
- package/internal/a11y-runtime.js +234 -0
- package/internal/a11y.js +102 -0
- package/internal/assets.js +366 -18
- package/internal/barrel-imports.js +459 -0
- package/internal/compile-assets.js +67 -0
- package/internal/config.js +30 -3
- package/internal/dev-state.js +161 -0
- package/internal/devtools.js +117 -0
- package/internal/diagnostics.js +369 -0
- package/internal/events.js +8 -5
- package/internal/flight.js +803 -0
- package/internal/flow-keywords.js +1 -1
- package/internal/frontmatter.js +33 -0
- package/internal/http.js +21 -2
- package/internal/image-endpoint.js +118 -0
- package/internal/instrumentation.js +23 -0
- package/internal/module-graph.js +134 -0
- package/internal/native-web.js +26 -0
- package/internal/openapi.js +218 -0
- package/internal/relay.js +74 -0
- package/internal/routes.js +1294 -81
- package/internal/rsc.js +394 -11
- package/internal/serve.js +583 -29
- package/internal/server-components.js +109 -0
- package/internal/worker-builtins.js +170 -0
- package/package.json +28 -6
package/internal/assets.js
CHANGED
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
// imports", `resolveId` + `load` + `generateBundle` + `writeBundle` +
|
|
10
10
|
// `transformIndexHtml` — since before there was anything behind it, and
|
|
11
11
|
// `uf inspect` has been listing it in the resolved pipeline. This is the
|
|
12
|
-
// implementation of a plugin uf was already claiming to run
|
|
12
|
+
// implementation of a plugin uf was already claiming to run — and `resolveId`
|
|
13
|
+
// and `generateBundle`, declared there from the start and unimplemented until
|
|
14
|
+
// icons arrived, are now real: the first resolves `uf:icon/…`, the second
|
|
15
|
+
// assembles the sprite once the graph is complete.
|
|
13
16
|
//
|
|
14
17
|
// # What an import becomes
|
|
15
18
|
//
|
|
@@ -45,6 +48,19 @@
|
|
|
45
48
|
// two must agree": there is one pipeline and one set of bytes, and the only
|
|
46
49
|
// thing that differs between them is the URL prefix they are served under.
|
|
47
50
|
//
|
|
51
|
+
// # Icons, and why they are not `.svg`
|
|
52
|
+
//
|
|
53
|
+
// `import Star from "uf:icon/star"` resolves against the directory
|
|
54
|
+
// `app.builtins.icons.dir` names, and `import sprite from "uf:icon-sprite"` is
|
|
55
|
+
// the sprite built from every icon the build reached. Neither claims an
|
|
56
|
+
// extension, which is the point: `.svg` stays Vite's, so `vite-plugin-svgr`
|
|
57
|
+
// and everything like it keep working, and uf adds a capability in its own
|
|
58
|
+
// namespace instead of taking one away.
|
|
59
|
+
//
|
|
60
|
+
// The sprite is emitted in `generateBundle`, which is the first hook that runs
|
|
61
|
+
// after every module has been loaded — and therefore the first moment the set
|
|
62
|
+
// of icons a build reached is the whole set.
|
|
63
|
+
//
|
|
48
64
|
// # What this plugin deliberately does not claim
|
|
49
65
|
//
|
|
50
66
|
// An import with a query — `./hero.png?url`, `?raw`, `?inline` — is left to
|
|
@@ -57,7 +73,70 @@
|
|
|
57
73
|
import { existsSync, readFileSync } from "node:fs";
|
|
58
74
|
import path from "node:path";
|
|
59
75
|
|
|
60
|
-
import {
|
|
76
|
+
import {
|
|
77
|
+
AssetService,
|
|
78
|
+
ICON_PREFIX,
|
|
79
|
+
ICON_SPRITE,
|
|
80
|
+
OG_EXTENSION,
|
|
81
|
+
assetKind,
|
|
82
|
+
} from "@uniflowed/host/assets";
|
|
83
|
+
|
|
84
|
+
import { endpointSettings, localImageEndpoint, servesRemoteImages } from "./image-endpoint.js";
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Whether `id` is `@uniflowed/web`'s `internal/image-endpoint.js`.
|
|
88
|
+
*
|
|
89
|
+
* By path *and* by the package it is in, because the suffix alone is a name
|
|
90
|
+
* any package could have; the manifest two directories up has to say
|
|
91
|
+
* `@uniflowed/web`. The answer is remembered per directory, so the manifest is
|
|
92
|
+
* read once rather than once per module id.
|
|
93
|
+
*/
|
|
94
|
+
const webPackageDirectories = new Map();
|
|
95
|
+
function isWebImageEndpoint(id) {
|
|
96
|
+
const file = id.split("?")[0];
|
|
97
|
+
if (
|
|
98
|
+
!file.endsWith(`${path.sep}internal${path.sep}image-endpoint.js`) &&
|
|
99
|
+
!file.endsWith("/internal/image-endpoint.js")
|
|
100
|
+
) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
const directory = path.dirname(path.dirname(file));
|
|
104
|
+
if (!webPackageDirectories.has(directory)) {
|
|
105
|
+
let name = null;
|
|
106
|
+
try {
|
|
107
|
+
name = JSON.parse(readFileSync(path.join(directory, "package.json"), "utf8")).name;
|
|
108
|
+
} catch {
|
|
109
|
+
name = null;
|
|
110
|
+
}
|
|
111
|
+
webPackageDirectories.set(directory, name === "@uniflowed/web");
|
|
112
|
+
}
|
|
113
|
+
return webPackageDirectories.get(directory);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The source `@uniflowed/web/internal/image-endpoint.js` is answered with.
|
|
118
|
+
*
|
|
119
|
+
* The same shape as the file, with the project's values: the endpoint's path
|
|
120
|
+
* under Vite's `base` when `remotePatterns` lists anything, and `null`
|
|
121
|
+
* otherwise, which is the file's own answer — so a project with no remote
|
|
122
|
+
* images renders exactly what it rendered before the endpoint existed. The
|
|
123
|
+
* widths and quality are the ones the endpoint will accept, defaulted the way
|
|
124
|
+
* `@uniflowed/server/image` defaults them, so the `srcset` `Image` writes
|
|
125
|
+
* names no width the endpoint refuses.
|
|
126
|
+
*
|
|
127
|
+
* @param {object} images `app.builtins.images`
|
|
128
|
+
* @param {string} base Vite's resolved `base`
|
|
129
|
+
*/
|
|
130
|
+
export async function imageEndpointSource(images, base) {
|
|
131
|
+
const defaults = await import("@uniflowed/server/image");
|
|
132
|
+
const { widths, quality } = endpointSettings(images, defaults);
|
|
133
|
+
const endpoint = servesRemoteImages(images) ? `${base}__uf/image` : null;
|
|
134
|
+
return (
|
|
135
|
+
"// Generated by uf:asset from app.builtins.images. See @uniflowed/web's\n" +
|
|
136
|
+
"// internal/image-endpoint.js for the file this stands in for.\n" +
|
|
137
|
+
`export const IMAGE_ENDPOINT = ${JSON.stringify({ path: endpoint, widths, quality })};\n`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
61
140
|
|
|
62
141
|
/** Where transformed assets are kept, relative to the project root. */
|
|
63
142
|
export const CACHE_DIR = ".uf/cache/assets";
|
|
@@ -71,6 +150,36 @@ export const CACHE_DIR = ".uf/cache/assets";
|
|
|
71
150
|
*/
|
|
72
151
|
export const DEV_PREFIX = "@uf-asset/";
|
|
73
152
|
|
|
153
|
+
/**
|
|
154
|
+
* What stands in for the sprite until `generateBundle` knows what is in it.
|
|
155
|
+
*
|
|
156
|
+
* A string no source file would contain, replaced in the generated chunk once
|
|
157
|
+
* every module has been loaded. The module graph needs *something* at `load`
|
|
158
|
+
* time and the sprite is not knowable then; see the `sprite` branch of `load`.
|
|
159
|
+
*/
|
|
160
|
+
const SPRITE_PLACEHOLDER = "__UF_ICON_SPRITE__";
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* What a `*.og.json` import resolves to.
|
|
164
|
+
*
|
|
165
|
+
* A card cannot keep its own id, and the reason is worth writing down because
|
|
166
|
+
* nothing about `enforce: "pre"` prevents it. Vite's JSON handling is a
|
|
167
|
+
* *native* rolldown plugin, `builtin:vite-json`, and it selects modules by
|
|
168
|
+
* **id**: anything still ending in `.json` when its `transform` runs is put
|
|
169
|
+
* through a JSON parser, whatever an earlier `load` returned. Ordering the
|
|
170
|
+
* hooks does not help, because the module this plugin loads is JavaScript
|
|
171
|
+
* under a name that says JSON — and the parser's answer is
|
|
172
|
+
* `expected value at line 1 column 1`.
|
|
173
|
+
*
|
|
174
|
+
* So the id changes rather than the order. The card resolves to
|
|
175
|
+
* `\0uf-og:<absolute path>.js`: `\0` is the convention for a module that is
|
|
176
|
+
* not a file, and the trailing extension is what takes it out of every
|
|
177
|
+
* `.json` filter in the pipeline.
|
|
178
|
+
*/
|
|
179
|
+
const OG_PREFIX = "\0uf-og:";
|
|
180
|
+
/** Appended to that id so it does not end in `.json`. See [`OG_PREFIX`]. */
|
|
181
|
+
const OG_SUFFIX = ".js";
|
|
182
|
+
|
|
74
183
|
/**
|
|
75
184
|
* The module source for one transformed asset.
|
|
76
185
|
*
|
|
@@ -146,15 +255,20 @@ export function withUrls(image, baseUrl) {
|
|
|
146
255
|
* @param {object} options
|
|
147
256
|
* @param {object} [options.images] `app.builtins.images`
|
|
148
257
|
* @param {object} [options.fonts] `app.builtins.fonts`
|
|
258
|
+
* @param {object} [options.icons] `app.builtins.icons`
|
|
259
|
+
* @param {object} [options.og] `app.builtins.og`
|
|
149
260
|
* @param {string} [options.command] the `uf` binary to transform through
|
|
150
261
|
*/
|
|
151
|
-
export function assetPlugin({ images = {}, fonts = {}, command } = {}) {
|
|
262
|
+
export function assetPlugin({ images = {}, fonts = {}, icons = {}, og = {}, command } = {}) {
|
|
152
263
|
// Both halves can be turned off independently, and a plugin that is off is
|
|
153
264
|
// still in the array: `uf inspect` lists the resolved pipeline, and a
|
|
154
265
|
// pipeline that changes shape when a feature is disabled is a pipeline whose
|
|
155
266
|
// listing cannot be compared between two projects.
|
|
156
267
|
const imagesOn = images.enabled !== false;
|
|
157
268
|
const fontsOn = fonts.enabled !== false;
|
|
269
|
+
const iconsOn = icons.enabled !== false;
|
|
270
|
+
const ogOn = og.enabled !== false;
|
|
271
|
+
const iconDir = icons.dir ?? "icons";
|
|
158
272
|
|
|
159
273
|
let root = process.cwd();
|
|
160
274
|
let base = "/";
|
|
@@ -174,6 +288,18 @@ export function assetPlugin({ images = {}, fonts = {}, command } = {}) {
|
|
|
174
288
|
* redo every image, which is the whole cost this map exists to avoid.
|
|
175
289
|
*/
|
|
176
290
|
const transformed = new Map();
|
|
291
|
+
/** Whether anything imported the sprite at all. */
|
|
292
|
+
let spriteRequested = false;
|
|
293
|
+
/**
|
|
294
|
+
* Every icon this build has reached, by symbol id.
|
|
295
|
+
*
|
|
296
|
+
* Here and not in the `uf assets` process, because this closure outlives it:
|
|
297
|
+
* a build closes the service in `buildEnd`, which runs *before*
|
|
298
|
+
* `generateBundle`, and a build with a client environment and a server one
|
|
299
|
+
* has two bundles and one set of icons between them. The plugin is what
|
|
300
|
+
* spans a build, so the plugin is what remembers.
|
|
301
|
+
*/
|
|
302
|
+
const reachedIcons = new Map();
|
|
177
303
|
|
|
178
304
|
const cacheDir = () => path.resolve(root, CACHE_DIR);
|
|
179
305
|
/**
|
|
@@ -206,9 +332,32 @@ export function assetPlugin({ images = {}, fonts = {}, command } = {}) {
|
|
|
206
332
|
const kind = assetKind(id);
|
|
207
333
|
if (kind === "image" && !imagesOn) return null;
|
|
208
334
|
if (kind === "font" && !fontsOn) return null;
|
|
335
|
+
if (kind === "og" && !ogOn) return null;
|
|
336
|
+
if ((kind === "icon" || kind === "sprite") && !iconsOn) return null;
|
|
209
337
|
return kind;
|
|
210
338
|
};
|
|
211
339
|
|
|
340
|
+
/**
|
|
341
|
+
* The file one `uf:icon/<name>` names.
|
|
342
|
+
*
|
|
343
|
+
* The name is a path segment and nothing else. `..` in it would reach out of
|
|
344
|
+
* the icon directory and turn an import into a way to read the repository,
|
|
345
|
+
* so it is rejected rather than resolved — the same rule the dev middleware
|
|
346
|
+
* applies to an emitted file name.
|
|
347
|
+
*/
|
|
348
|
+
const iconFile = (id) => {
|
|
349
|
+
const name = id.slice(ICON_PREFIX.length);
|
|
350
|
+
if (name === "" || name.includes("..") || path.isAbsolute(name)) return null;
|
|
351
|
+
return { name, file: path.resolve(root, iconDir, `${name}.svg`) };
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/** Tell a dev server the sprite it already sent is missing an icon. */
|
|
355
|
+
const invalidateSprite = () => {
|
|
356
|
+
if (server == null || !spriteRequested) return;
|
|
357
|
+
const module = server.moduleGraph.getModuleById(ICON_SPRITE);
|
|
358
|
+
if (module != null) server.moduleGraph.invalidateModule(module);
|
|
359
|
+
};
|
|
360
|
+
|
|
212
361
|
return {
|
|
213
362
|
name: "uf:asset",
|
|
214
363
|
// Before Vite's own asset handling, which would otherwise claim the same
|
|
@@ -222,9 +371,98 @@ export function assetPlugin({ images = {}, fonts = {}, command } = {}) {
|
|
|
222
371
|
isBuild = config.command === "build";
|
|
223
372
|
},
|
|
224
373
|
|
|
374
|
+
// `uf:icon/…` and `uf:icon-sprite` are uf's own ids and resolve to
|
|
375
|
+
// themselves. Returning the id unchanged rather than a `\0`-prefixed one
|
|
376
|
+
// keeps it readable in a stack trace and in `vite --debug`, and nothing
|
|
377
|
+
// else in the pipeline claims the `uf:` scheme.
|
|
378
|
+
//
|
|
379
|
+
// A card is the opposite case: it has to *lose* its name, because the name
|
|
380
|
+
// is what the native JSON plugin claims it by. See `OG_PREFIX`.
|
|
381
|
+
async resolveId(id, importer, options) {
|
|
382
|
+
if (iconsOn && (id === ICON_SPRITE || id.startsWith(ICON_PREFIX))) return id;
|
|
383
|
+
if (!ogOn || !id.toLowerCase().endsWith(OG_EXTENSION)) return null;
|
|
384
|
+
// Compared against the id as written, so a query is never claimed:
|
|
385
|
+
// `./card.og.json?raw` and `?url` still reach the file, because a query
|
|
386
|
+
// is Vite's.
|
|
387
|
+
const resolved = await this.resolve(id, importer, { ...options, skipSelf: true });
|
|
388
|
+
if (resolved == null || resolved.external || !existsSync(resolved.id)) return resolved;
|
|
389
|
+
return `${OG_PREFIX}${resolved.id}${OG_SUFFIX}`;
|
|
390
|
+
},
|
|
391
|
+
|
|
225
392
|
async load(id) {
|
|
393
|
+
// `@uniflowed/web`'s idea of the image endpoint, answered with the
|
|
394
|
+
// project's. See `imageEndpointSource`.
|
|
395
|
+
if (isWebImageEndpoint(id)) {
|
|
396
|
+
return imageEndpointSource(imagesOn ? images : { enabled: false }, base);
|
|
397
|
+
}
|
|
398
|
+
if (id.startsWith(OG_PREFIX)) {
|
|
399
|
+
const file = id.slice(OG_PREFIX.length, -OG_SUFFIX.length);
|
|
400
|
+
// Watched by hand, because the module id is no longer the file's path
|
|
401
|
+
// and nothing else would associate the two. Without this a dev server
|
|
402
|
+
// never redraws a card whose template was edited.
|
|
403
|
+
this.addWatchFile(file);
|
|
404
|
+
return loadAsset.call(this, {
|
|
405
|
+
kind: "og",
|
|
406
|
+
file,
|
|
407
|
+
transformed,
|
|
408
|
+
service: ensureService(),
|
|
409
|
+
cacheDir: cacheDir(),
|
|
410
|
+
baseUrl: baseUrl(),
|
|
411
|
+
assetsDir,
|
|
412
|
+
isBuild,
|
|
413
|
+
images,
|
|
414
|
+
fonts,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
226
418
|
const kind = claims(id);
|
|
227
419
|
if (kind == null) return null;
|
|
420
|
+
|
|
421
|
+
if (kind === "sprite") {
|
|
422
|
+
spriteRequested = true;
|
|
423
|
+
// A build cannot know the sprite here: `load` runs while the graph is
|
|
424
|
+
// still being walked, so the set of icons reached so far is not the
|
|
425
|
+
// set. It emits a placeholder that `generateBundle` — the first hook
|
|
426
|
+
// after every module has been loaded — replaces with the real markup.
|
|
427
|
+
//
|
|
428
|
+
// A dev server has no `generateBundle`, so it assembles from what has
|
|
429
|
+
// been reached and invalidates this module whenever a new icon turns
|
|
430
|
+
// up. That costs one extra reload the first time a page introduces an
|
|
431
|
+
// icon and is exactly right afterwards, which is the trade a dev
|
|
432
|
+
// server makes everywhere else too.
|
|
433
|
+
if (isBuild) return assetModuleSource({ markup: SPRITE_PLACEHOLDER });
|
|
434
|
+
const sprite = await ensureService().sprite({
|
|
435
|
+
outDir: cacheDir(),
|
|
436
|
+
icons: [...reachedIcons.values()],
|
|
437
|
+
});
|
|
438
|
+
return assetModuleSource({ markup: sprite.markup });
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (kind === "icon") {
|
|
442
|
+
const resolved = iconFile(id);
|
|
443
|
+
if (resolved == null) return null;
|
|
444
|
+
if (!existsSync(resolved.file)) {
|
|
445
|
+
this.error(
|
|
446
|
+
`${id} does not exist: uf looked for ${path.relative(root, resolved.file)}. ` +
|
|
447
|
+
"`app.builtins.icons.dir` is where `uf:icon/…` resolves against.",
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
const asset = await ensureService().icon(resolved.file, {
|
|
451
|
+
outDir: cacheDir(),
|
|
452
|
+
name: resolved.name,
|
|
453
|
+
});
|
|
454
|
+
this.addWatchFile(resolved.file);
|
|
455
|
+
reachedIcons.set(asset.id, asset);
|
|
456
|
+
invalidateSprite();
|
|
457
|
+
return assetModuleSource({
|
|
458
|
+
id: asset.id,
|
|
459
|
+
href: `#${asset.id}`,
|
|
460
|
+
viewBox: asset.viewBox,
|
|
461
|
+
width: asset.width,
|
|
462
|
+
height: asset.height,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
228
466
|
const file = path.resolve(id);
|
|
229
467
|
// Not this plugin's to fail on: an id with one of these extensions that
|
|
230
468
|
// is not a file on disk is a virtual module somebody else owns.
|
|
@@ -244,6 +482,38 @@ export function assetPlugin({ images = {}, fonts = {}, command } = {}) {
|
|
|
244
482
|
});
|
|
245
483
|
},
|
|
246
484
|
|
|
485
|
+
// After every module has been loaded, which is the first moment the set of
|
|
486
|
+
// icons this build reached is the whole set. A sprite assembled in `load`
|
|
487
|
+
// would hold the icons reached *so far*, which is a different sprite on
|
|
488
|
+
// every run depending on module order.
|
|
489
|
+
async generateBundle(_options, bundle) {
|
|
490
|
+
if (!iconsOn || !spriteRequested) return;
|
|
491
|
+
const sprite = await ensureService().sprite({
|
|
492
|
+
outDir: cacheDir(),
|
|
493
|
+
icons: [...reachedIcons.values()],
|
|
494
|
+
});
|
|
495
|
+
// Inlined into the chunk and *not* emitted as a file of its own. An
|
|
496
|
+
// external sprite would be the better answer if it worked — one file
|
|
497
|
+
// cached across every page — but `<use href="sprite.svg#id">` does not
|
|
498
|
+
// resolve across documents in any version of Safari and is blocked
|
|
499
|
+
// cross-origin in Chrome, so the file would be dead weight in `dist/`
|
|
500
|
+
// and in `uf_bundle`'s size report. `uf assets` still writes it into the
|
|
501
|
+
// cache directory, which is where the dev server reads it from.
|
|
502
|
+
//
|
|
503
|
+
// The placeholder is replaced rather than the module re-run: by
|
|
504
|
+
// `generateBundle` the chunk is already generated, and the sprite is one
|
|
505
|
+
// string literal in it.
|
|
506
|
+
//
|
|
507
|
+
// A function replacement, because a plain string one would interpret
|
|
508
|
+
// `$&` and `$'` — and an icon is somebody else's markup.
|
|
509
|
+
const escaped = JSON.stringify(sprite.markup).slice(1, -1);
|
|
510
|
+
for (const chunk of Object.values(bundle)) {
|
|
511
|
+
if (chunk.type === "chunk" && chunk.code.includes(SPRITE_PLACEHOLDER)) {
|
|
512
|
+
chunk.code = chunk.code.replaceAll(SPRITE_PLACEHOLDER, () => escaped);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
|
|
247
517
|
configureServer(devServer) {
|
|
248
518
|
server = devServer;
|
|
249
519
|
devServer.httpServer?.once("close", () => {
|
|
@@ -276,6 +546,44 @@ export function assetPlugin({ images = {}, fonts = {}, command } = {}) {
|
|
|
276
546
|
response.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
277
547
|
response.end(readFileSync(target));
|
|
278
548
|
});
|
|
549
|
+
|
|
550
|
+
// `/__uf/image`, so an `Image` with a remote `src` renders under
|
|
551
|
+
// `uf dev` exactly as it will under `uf start`: the same endpoint, the
|
|
552
|
+
// same allow-list, the same encoder. Mounted only for a project that
|
|
553
|
+
// listed remote hosts, and built on the first request for it rather
|
|
554
|
+
// than with the server. See `./image-endpoint.js`.
|
|
555
|
+
if (imagesOn && servesRemoteImages(images)) {
|
|
556
|
+
const endpointPath = `${base}__uf/image`;
|
|
557
|
+
let ready = null;
|
|
558
|
+
devServer.middlewares.use((request, response, next) => {
|
|
559
|
+
const url = request.url ?? "";
|
|
560
|
+
const queryAt = url.indexOf("?");
|
|
561
|
+
if ((queryAt === -1 ? url : url.slice(0, queryAt)) !== endpointPath) return next();
|
|
562
|
+
ready ??= Promise.all([
|
|
563
|
+
localImageEndpoint({ images, root, command }),
|
|
564
|
+
import("@uniflowed/server/node"),
|
|
565
|
+
]);
|
|
566
|
+
ready
|
|
567
|
+
.then(async ([endpoint, { send }]) => {
|
|
568
|
+
// The endpoint reads the query and `Accept` and nothing else, so
|
|
569
|
+
// that is all the request it is handed carries.
|
|
570
|
+
const accept = request.headers.accept;
|
|
571
|
+
const asked = new Request(
|
|
572
|
+
`http://localhost/__uf/image${queryAt === -1 ? "" : url.slice(queryAt)}`,
|
|
573
|
+
{
|
|
574
|
+
method: request.method,
|
|
575
|
+
headers: typeof accept === "string" ? { accept } : {},
|
|
576
|
+
},
|
|
577
|
+
);
|
|
578
|
+
const answered = await endpoint(asked);
|
|
579
|
+
if (answered == null) return next();
|
|
580
|
+
await send(response, answered);
|
|
581
|
+
return undefined;
|
|
582
|
+
})
|
|
583
|
+
.catch(next);
|
|
584
|
+
return undefined;
|
|
585
|
+
});
|
|
586
|
+
}
|
|
279
587
|
},
|
|
280
588
|
|
|
281
589
|
watchChange(id) {
|
|
@@ -288,6 +596,7 @@ export function assetPlugin({ images = {}, fonts = {}, command } = {}) {
|
|
|
288
596
|
// files and leaves the old ones for anything still holding a URL.
|
|
289
597
|
transformed.delete(`image:${path.resolve(id)}`);
|
|
290
598
|
transformed.delete(`font:${path.resolve(id)}`);
|
|
599
|
+
transformed.delete(`og:${path.resolve(id)}`);
|
|
291
600
|
},
|
|
292
601
|
|
|
293
602
|
buildEnd() {
|
|
@@ -319,24 +628,38 @@ async function loadAsset(context) {
|
|
|
319
628
|
const key = `${kind}:${file}`;
|
|
320
629
|
let manifest = transformed.get(key);
|
|
321
630
|
if (manifest == null) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
631
|
+
if (kind === "image") {
|
|
632
|
+
manifest = await service.image(file, {
|
|
633
|
+
outDir: cacheDir,
|
|
634
|
+
widths: images.widths,
|
|
635
|
+
quality: images.quality,
|
|
636
|
+
blur: images.placeholder,
|
|
637
|
+
});
|
|
638
|
+
} else if (kind === "og") {
|
|
639
|
+
manifest = await service.og(file, { outDir: cacheDir });
|
|
640
|
+
} else {
|
|
641
|
+
manifest = await service.font(file, {
|
|
642
|
+
outDir: cacheDir,
|
|
643
|
+
family: fonts.family,
|
|
644
|
+
display: fonts.display,
|
|
645
|
+
subset: fonts.subset,
|
|
646
|
+
preload: fonts.preload,
|
|
647
|
+
baseUrl,
|
|
648
|
+
});
|
|
649
|
+
}
|
|
336
650
|
transformed.set(key, manifest);
|
|
337
651
|
}
|
|
338
652
|
|
|
339
|
-
const files =
|
|
653
|
+
const files =
|
|
654
|
+
kind === "image"
|
|
655
|
+
? manifest.variants.map((v) => v.file)
|
|
656
|
+
: kind === "og"
|
|
657
|
+
? [manifest.file]
|
|
658
|
+
: // Every bucket, not just the primary: a `unicode-range` split emits
|
|
659
|
+
// one file per script and the browser fetches whichever the page
|
|
660
|
+
// needs. Emitting only the one the manifest calls primary would
|
|
661
|
+
// leave the others named in the stylesheet and absent from `dist/`.
|
|
662
|
+
manifest.faces.map((face) => face.file);
|
|
340
663
|
if (isBuild) {
|
|
341
664
|
// Handed to Rollup rather than copied by hand, so the bundler owns what
|
|
342
665
|
// lands in the output directory and `uf_bundle`'s size report — which
|
|
@@ -356,6 +679,18 @@ async function loadAsset(context) {
|
|
|
356
679
|
if (kind === "image") {
|
|
357
680
|
return assetModuleSource(withUrls(manifest, baseUrl));
|
|
358
681
|
}
|
|
682
|
+
if (kind === "og") {
|
|
683
|
+
return assetModuleSource({
|
|
684
|
+
// The shape `Metadata.openGraph.images` and `OgImage` both read. A URL,
|
|
685
|
+
// a size and the alt text, which is all a card ever is to a page.
|
|
686
|
+
url: `${baseUrl}${manifest.file}`,
|
|
687
|
+
width: manifest.width,
|
|
688
|
+
height: manifest.height,
|
|
689
|
+
type: manifest.mime,
|
|
690
|
+
alt: manifest.alt,
|
|
691
|
+
bytes: manifest.bytes,
|
|
692
|
+
});
|
|
693
|
+
}
|
|
359
694
|
return assetModuleSource({
|
|
360
695
|
src: `${baseUrl}${manifest.file}`,
|
|
361
696
|
family: manifest.family,
|
|
@@ -373,6 +708,19 @@ async function loadAsset(context) {
|
|
|
373
708
|
metrics: manifest.metrics,
|
|
374
709
|
fallback: manifest.fallback,
|
|
375
710
|
fallbackDeclined: manifest.fallbackDeclined,
|
|
711
|
+
// Every emitted file with its URL, so `Font` can preload exactly the one
|
|
712
|
+
// marked rather than all of them — which is the one thing that would undo
|
|
713
|
+
// a `unicode-range` split.
|
|
714
|
+
faces: (manifest.faces ?? []).map((face) => ({
|
|
715
|
+
...face,
|
|
716
|
+
url: `${baseUrl}${face.file}`,
|
|
717
|
+
})),
|
|
718
|
+
subset: manifest.subset ?? null,
|
|
719
|
+
// Carried through for the same reason `declined` is on an image: a project
|
|
720
|
+
// that asked for a subset and got the whole font is entitled to the
|
|
721
|
+
// sentence saying why, without reading this plugin.
|
|
722
|
+
subsetDeclined: manifest.subsetDeclined ?? null,
|
|
723
|
+
sourceBytes: manifest.sourceBytes ?? null,
|
|
376
724
|
});
|
|
377
725
|
}
|
|
378
726
|
|