@t09tanaka/stoneage 0.1.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/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +768 -0
- package/dist/agent-skill.d.ts +1 -0
- package/dist/agent-skill.js +140 -0
- package/dist/assets.d.ts +125 -0
- package/dist/assets.js +341 -0
- package/dist/cli.d.ts +236 -0
- package/dist/cli.js +3077 -0
- package/dist/core.d.ts +473 -0
- package/dist/core.js +2897 -0
- package/dist/data.d.ts +121 -0
- package/dist/data.js +358 -0
- package/dist/deploy.d.ts +34 -0
- package/dist/deploy.js +203 -0
- package/dist/dev.d.ts +36 -0
- package/dist/dev.js +313 -0
- package/dist/example.d.ts +134 -0
- package/dist/example.js +1272 -0
- package/dist/fragment/client.d.ts +13 -0
- package/dist/fragment/client.js +150 -0
- package/dist/fragment.d.ts +15 -0
- package/dist/fragment.js +27 -0
- package/dist/html.d.ts +57 -0
- package/dist/html.js +208 -0
- package/dist/images.d.ts +95 -0
- package/dist/images.js +292 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/migration.d.ts +157 -0
- package/dist/migration.js +983 -0
- package/dist/optimize.d.ts +15 -0
- package/dist/optimize.js +215 -0
- package/dist/pagination.d.ts +26 -0
- package/dist/pagination.js +62 -0
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +10 -0
- package/dist/search.d.ts +32 -0
- package/dist/search.js +55 -0
- package/dist/testing.d.ts +66 -0
- package/dist/testing.js +97 -0
- package/dist/validate.d.ts +2 -0
- package/dist/validate.js +1 -0
- package/jsx-loader.mjs +52 -0
- package/jsx-register.mjs +7 -0
- package/package.json +135 -0
- package/tsconfig.base.json +16 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FragmentTrigger } from "../fragment.js";
|
|
2
|
+
export type FragmentControllerOptions = {
|
|
3
|
+
root?: ParentNode;
|
|
4
|
+
trigger?: FragmentTrigger;
|
|
5
|
+
fetch?: typeof fetch;
|
|
6
|
+
};
|
|
7
|
+
export type FragmentController = {
|
|
8
|
+
elements: Element[];
|
|
9
|
+
load: (element: Element) => Promise<void>;
|
|
10
|
+
disconnect: () => void;
|
|
11
|
+
};
|
|
12
|
+
export declare function fragmentController(selector?: string, options?: FragmentControllerOptions): FragmentController;
|
|
13
|
+
export declare function loadFragment(element: Element, fetcher?: typeof fetch): Promise<void>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
export function fragmentController(selector = "[data-stoneage-fragment]", options = {}) {
|
|
2
|
+
const root = options.root ?? document;
|
|
3
|
+
const fetcher = options.fetch ?? fetch;
|
|
4
|
+
const elements = Array.from(root.querySelectorAll(selector));
|
|
5
|
+
const cleanups = [];
|
|
6
|
+
const armedElements = new WeakSet();
|
|
7
|
+
const controller = {
|
|
8
|
+
elements,
|
|
9
|
+
load: async (element) => {
|
|
10
|
+
await loadFragmentElement(element, fetcher);
|
|
11
|
+
armNestedFragments(element, selector, armElement);
|
|
12
|
+
},
|
|
13
|
+
disconnect: () => {
|
|
14
|
+
for (const cleanup of cleanups.splice(0)) {
|
|
15
|
+
cleanup();
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
const armElement = (element) => {
|
|
20
|
+
if (armedElements.has(element)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
armedElements.add(element);
|
|
24
|
+
const trigger = options.trigger ?? readFragmentTrigger(element);
|
|
25
|
+
if (trigger === "manual") {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (trigger === "details-open") {
|
|
29
|
+
cleanups.push(loadWhenDetailsOpen(element, controller.load));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (trigger === "idle") {
|
|
33
|
+
cleanups.push(loadWhenIdle(element, controller.load));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
cleanups.push(loadWhenVisible(element, controller.load));
|
|
37
|
+
};
|
|
38
|
+
for (const element of elements) {
|
|
39
|
+
armElement(element);
|
|
40
|
+
}
|
|
41
|
+
return controller;
|
|
42
|
+
}
|
|
43
|
+
export async function loadFragment(element, fetcher = fetch) {
|
|
44
|
+
await loadFragmentElement(element, fetcher);
|
|
45
|
+
}
|
|
46
|
+
async function loadFragmentElement(element, fetcher) {
|
|
47
|
+
if (element.hasAttribute("data-fragment-loaded") ||
|
|
48
|
+
element.hasAttribute("data-fragment-loading")) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const src = element.getAttribute("data-fragment-src");
|
|
52
|
+
if (!src) {
|
|
53
|
+
throw new Error("Deferred fragment placeholder is missing data-fragment-src.");
|
|
54
|
+
}
|
|
55
|
+
element.setAttribute("data-fragment-loading", "true");
|
|
56
|
+
element.removeAttribute("data-fragment-error");
|
|
57
|
+
dispatchFragmentEvent(element, "stoneage:fragmentbeforeload", { src });
|
|
58
|
+
try {
|
|
59
|
+
const response = await fetcher(src, {
|
|
60
|
+
headers: { accept: "text/html" },
|
|
61
|
+
});
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new Error(`Failed to load deferred fragment ${src}: ${response.status}`);
|
|
64
|
+
}
|
|
65
|
+
element.innerHTML = await response.text();
|
|
66
|
+
element.setAttribute("data-fragment-loaded", "true");
|
|
67
|
+
element.removeAttribute("data-fragment-error");
|
|
68
|
+
dispatchFragmentEvent(element, "stoneage:fragmentload", { src });
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
element.setAttribute("data-fragment-error", "true");
|
|
72
|
+
dispatchFragmentEvent(element, "stoneage:fragmenterror", { src, error });
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
element.removeAttribute("data-fragment-loading");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function dispatchFragmentEvent(element, type, detail) {
|
|
80
|
+
element.dispatchEvent(new CustomEvent(type, {
|
|
81
|
+
bubbles: true,
|
|
82
|
+
detail,
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
function readFragmentTrigger(element) {
|
|
86
|
+
const trigger = element.getAttribute("data-fragment-trigger");
|
|
87
|
+
if (trigger === "manual" ||
|
|
88
|
+
trigger === "details-open" ||
|
|
89
|
+
trigger === "idle" ||
|
|
90
|
+
trigger === "visible") {
|
|
91
|
+
return trigger;
|
|
92
|
+
}
|
|
93
|
+
return "visible";
|
|
94
|
+
}
|
|
95
|
+
function armNestedFragments(element, selector, armElement) {
|
|
96
|
+
for (const nested of Array.from(element.querySelectorAll(selector))) {
|
|
97
|
+
armElement(nested);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function loadWhenDetailsOpen(element, load) {
|
|
101
|
+
const details = element.closest("details");
|
|
102
|
+
if (!details) {
|
|
103
|
+
return loadWhenVisible(element, load);
|
|
104
|
+
}
|
|
105
|
+
if (details.open) {
|
|
106
|
+
loadAutomatically(element, load);
|
|
107
|
+
return () => { };
|
|
108
|
+
}
|
|
109
|
+
const onToggle = () => {
|
|
110
|
+
if (details.open) {
|
|
111
|
+
loadAutomatically(element, load);
|
|
112
|
+
details.removeEventListener("toggle", onToggle);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
details.addEventListener("toggle", onToggle);
|
|
116
|
+
return () => details.removeEventListener("toggle", onToggle);
|
|
117
|
+
}
|
|
118
|
+
function loadWhenVisible(element, load) {
|
|
119
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
120
|
+
loadAutomatically(element, load);
|
|
121
|
+
return () => { };
|
|
122
|
+
}
|
|
123
|
+
const observer = new IntersectionObserver((entries) => {
|
|
124
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
125
|
+
loadAutomatically(element, load);
|
|
126
|
+
observer.disconnect();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
observer.observe(element);
|
|
130
|
+
return () => observer.disconnect();
|
|
131
|
+
}
|
|
132
|
+
function loadWhenIdle(element, load) {
|
|
133
|
+
const windowWithIdle = globalThis;
|
|
134
|
+
const handle = windowWithIdle.requestIdleCallback
|
|
135
|
+
? windowWithIdle.requestIdleCallback(() => loadAutomatically(element, load))
|
|
136
|
+
: setTimeout(() => loadAutomatically(element, load), 0);
|
|
137
|
+
return () => {
|
|
138
|
+
if (windowWithIdle.cancelIdleCallback) {
|
|
139
|
+
windowWithIdle.cancelIdleCallback(handle);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
clearTimeout(handle);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function loadAutomatically(element, load) {
|
|
147
|
+
void load(element).catch(() => {
|
|
148
|
+
// loadFragmentElement marks the placeholder with data-fragment-error.
|
|
149
|
+
});
|
|
150
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ClassValue, type HtmlMarkup, type HtmlNode } from "./html.js";
|
|
2
|
+
export type FragmentTrigger = "visible" | "details-open" | "idle" | "manual";
|
|
3
|
+
export type DeferredFragmentDefaults = {
|
|
4
|
+
defaultFallbackText?: string;
|
|
5
|
+
};
|
|
6
|
+
export type DeferredFragmentOptions = {
|
|
7
|
+
src: string;
|
|
8
|
+
trigger?: FragmentTrigger;
|
|
9
|
+
fallback?: HtmlNode;
|
|
10
|
+
as?: "div" | "span";
|
|
11
|
+
className?: ClassValue;
|
|
12
|
+
attrs?: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
export declare function deferredFragment(options: DeferredFragmentOptions): HtmlMarkup;
|
|
15
|
+
export declare function withDeferredFragmentDefaults<T>(defaults: DeferredFragmentDefaults, render: () => T): T;
|
package/dist/fragment.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { html } from "./html.js";
|
|
3
|
+
const defaultFallbackText = "Open deferred content";
|
|
4
|
+
const defaultsStorage = new AsyncLocalStorage();
|
|
5
|
+
export function deferredFragment(options) {
|
|
6
|
+
const fallback = options.fallback === undefined
|
|
7
|
+
? html("a", { href: options.src }, currentDefaultFallbackText())
|
|
8
|
+
: options.fallback;
|
|
9
|
+
return html(options.as ?? "div", {
|
|
10
|
+
"data-stoneage-fragment": true,
|
|
11
|
+
"data-fragment-src": options.src,
|
|
12
|
+
"data-fragment-trigger": options.trigger ?? "visible",
|
|
13
|
+
...fragmentAttrs(options.attrs ?? {}),
|
|
14
|
+
...(options.className !== undefined ? { className: options.className } : {}),
|
|
15
|
+
}, fallback);
|
|
16
|
+
}
|
|
17
|
+
export function withDeferredFragmentDefaults(defaults, render) {
|
|
18
|
+
return defaultsStorage.run(defaults, render);
|
|
19
|
+
}
|
|
20
|
+
function currentDefaultFallbackText() {
|
|
21
|
+
return defaultsStorage.getStore()?.defaultFallbackText ?? defaultFallbackText;
|
|
22
|
+
}
|
|
23
|
+
function fragmentAttrs(attrs) {
|
|
24
|
+
return Object.fromEntries(Object.entries(attrs).filter(([name]) => name !== "data-stoneage-fragment" &&
|
|
25
|
+
name !== "data-fragment-src" &&
|
|
26
|
+
name !== "data-fragment-trigger"));
|
|
27
|
+
}
|
package/dist/html.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
declare const markupSymbol: unique symbol;
|
|
2
|
+
export type HtmlNode = HtmlMarkup | string | number | boolean | null | undefined | HtmlNode[];
|
|
3
|
+
export type HtmlComponent<Props = Record<string, unknown>> = (props: Props & {
|
|
4
|
+
children?: HtmlNode[];
|
|
5
|
+
}) => HtmlNode;
|
|
6
|
+
export type HtmlMarkup = {
|
|
7
|
+
readonly [markupSymbol]: true;
|
|
8
|
+
readonly value: string;
|
|
9
|
+
toString(): string;
|
|
10
|
+
};
|
|
11
|
+
export type ClassValue = string | number | false | null | undefined | ClassValue[] | Record<string, unknown>;
|
|
12
|
+
export type StyleObject = Record<string, string | number | false | null | undefined>;
|
|
13
|
+
type IntrinsicProps = Record<string, unknown> & {
|
|
14
|
+
children?: HtmlNode[];
|
|
15
|
+
class?: ClassValue;
|
|
16
|
+
className?: ClassValue;
|
|
17
|
+
style?: string | StyleObject;
|
|
18
|
+
dangerouslySetInnerHTML?: {
|
|
19
|
+
__html: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export type IslandMountOptions = {
|
|
23
|
+
key?: string;
|
|
24
|
+
props?: unknown;
|
|
25
|
+
as?: "div" | "span";
|
|
26
|
+
className?: ClassValue;
|
|
27
|
+
attrs?: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
export type SvgImageOptions = {
|
|
30
|
+
src: string;
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
alt: string;
|
|
34
|
+
loading?: "eager" | "lazy";
|
|
35
|
+
decoding?: "async" | "sync" | "auto";
|
|
36
|
+
fetchPriority?: "high" | "low" | "auto";
|
|
37
|
+
className?: ClassValue;
|
|
38
|
+
attrs?: Record<string, unknown>;
|
|
39
|
+
};
|
|
40
|
+
export declare function html<Props extends Record<string, unknown>>(tag: string | HtmlComponent<Props>, props: Props | null, ...children: HtmlNode[]): HtmlMarkup;
|
|
41
|
+
export declare function Fragment(props: {
|
|
42
|
+
children?: HtmlNode[];
|
|
43
|
+
}): HtmlMarkup;
|
|
44
|
+
export declare function raw(value: string): HtmlMarkup;
|
|
45
|
+
export declare function classNames(...values: ClassValue[]): string;
|
|
46
|
+
export declare function islandMount(id: string, options?: IslandMountOptions): HtmlMarkup;
|
|
47
|
+
export declare function svgImage(options: SvgImageOptions): HtmlMarkup;
|
|
48
|
+
export declare function renderToString(node: HtmlNode): string;
|
|
49
|
+
declare global {
|
|
50
|
+
namespace JSX {
|
|
51
|
+
type Element = HtmlMarkup;
|
|
52
|
+
interface IntrinsicElements {
|
|
53
|
+
[elementName: string]: IntrinsicProps;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export {};
|
package/dist/html.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
const markupSymbol = Symbol("stoneage.html");
|
|
2
|
+
const voidElements = new Set([
|
|
3
|
+
"area",
|
|
4
|
+
"base",
|
|
5
|
+
"br",
|
|
6
|
+
"col",
|
|
7
|
+
"embed",
|
|
8
|
+
"hr",
|
|
9
|
+
"img",
|
|
10
|
+
"input",
|
|
11
|
+
"link",
|
|
12
|
+
"meta",
|
|
13
|
+
"source",
|
|
14
|
+
"track",
|
|
15
|
+
"wbr",
|
|
16
|
+
]);
|
|
17
|
+
export function html(tag, props, ...children) {
|
|
18
|
+
if (typeof tag === "function") {
|
|
19
|
+
return markup(renderNode(tag({ ...(props ?? {}), children })));
|
|
20
|
+
}
|
|
21
|
+
const normalizedProps = (props ?? {});
|
|
22
|
+
const attributes = renderAttributes(normalizedProps);
|
|
23
|
+
if (voidElements.has(tag)) {
|
|
24
|
+
return markup(`<${tag}${attributes}>`);
|
|
25
|
+
}
|
|
26
|
+
const content = normalizedProps.dangerouslySetInnerHTML
|
|
27
|
+
? normalizedProps.dangerouslySetInnerHTML.__html
|
|
28
|
+
: children.map(renderNode).join("");
|
|
29
|
+
return markup(`<${tag}${attributes}>${content}</${tag}>`);
|
|
30
|
+
}
|
|
31
|
+
export function Fragment(props) {
|
|
32
|
+
return markup((props.children ?? []).map(renderNode).join(""));
|
|
33
|
+
}
|
|
34
|
+
export function raw(value) {
|
|
35
|
+
return markup(value);
|
|
36
|
+
}
|
|
37
|
+
export function classNames(...values) {
|
|
38
|
+
return renderClassValue(values);
|
|
39
|
+
}
|
|
40
|
+
export function islandMount(id, options = {}) {
|
|
41
|
+
const tag = options.as ?? "div";
|
|
42
|
+
const hasKey = options.key !== undefined;
|
|
43
|
+
const key = options.key ?? id;
|
|
44
|
+
const root = html(tag, {
|
|
45
|
+
"data-island": id,
|
|
46
|
+
...(hasKey ? { "data-island-key": key } : {}),
|
|
47
|
+
...islandAttrs(options.attrs ?? {}),
|
|
48
|
+
...(options.className !== undefined ? { className: options.className } : {}),
|
|
49
|
+
});
|
|
50
|
+
if (options.props === undefined) {
|
|
51
|
+
return root;
|
|
52
|
+
}
|
|
53
|
+
return markup(`${root.value}<script type="application/json" data-island-props="${escapeAttribute(key)}">${escapeJsonScript(options.props)}</script>`);
|
|
54
|
+
}
|
|
55
|
+
export function svgImage(options) {
|
|
56
|
+
if (!options.src) {
|
|
57
|
+
throw new TypeError("svgImage src is required.");
|
|
58
|
+
}
|
|
59
|
+
if (options.width === undefined) {
|
|
60
|
+
throw new TypeError("svgImage width is required.");
|
|
61
|
+
}
|
|
62
|
+
if (options.height === undefined) {
|
|
63
|
+
throw new TypeError("svgImage height is required.");
|
|
64
|
+
}
|
|
65
|
+
if (options.alt === undefined) {
|
|
66
|
+
throw new TypeError("svgImage alt is required.");
|
|
67
|
+
}
|
|
68
|
+
return html("img", {
|
|
69
|
+
src: options.src,
|
|
70
|
+
width: options.width,
|
|
71
|
+
height: options.height,
|
|
72
|
+
alt: options.alt,
|
|
73
|
+
...(options.alt === "" ? { "aria-hidden": "true" } : {}),
|
|
74
|
+
loading: options.loading ?? "lazy",
|
|
75
|
+
decoding: options.decoding ?? "async",
|
|
76
|
+
...(options.fetchPriority ? { fetchPriority: options.fetchPriority } : {}),
|
|
77
|
+
...(options.className !== undefined ? { className: options.className } : {}),
|
|
78
|
+
...(options.attrs ?? {}),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export function renderToString(node) {
|
|
82
|
+
return renderNode(node);
|
|
83
|
+
}
|
|
84
|
+
function markup(value) {
|
|
85
|
+
const rendered = value;
|
|
86
|
+
return {
|
|
87
|
+
[markupSymbol]: true,
|
|
88
|
+
value: rendered,
|
|
89
|
+
toString: () => rendered,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function renderNode(node) {
|
|
93
|
+
if (node === null || node === undefined || typeof node === "boolean") {
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
if (Array.isArray(node)) {
|
|
97
|
+
return node.map(renderNode).join("");
|
|
98
|
+
}
|
|
99
|
+
if (isMarkup(node)) {
|
|
100
|
+
return node.value;
|
|
101
|
+
}
|
|
102
|
+
if (typeof node === "number") {
|
|
103
|
+
return String(node);
|
|
104
|
+
}
|
|
105
|
+
return escapeHtml(node);
|
|
106
|
+
}
|
|
107
|
+
function renderAttributes(props) {
|
|
108
|
+
let output = "";
|
|
109
|
+
for (const [rawName, value] of Object.entries(props)) {
|
|
110
|
+
if (rawName === "children" || rawName === "dangerouslySetInnerHTML") {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (value === false || value === null || value === undefined) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const name = normalizeAttributeName(rawName);
|
|
117
|
+
const renderedValue = renderAttributeValue(rawName, value);
|
|
118
|
+
if (renderedValue === undefined) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (renderedValue === true) {
|
|
122
|
+
output += ` ${name}`;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
output += ` ${name}="${escapeAttribute(renderedValue)}"`;
|
|
126
|
+
}
|
|
127
|
+
return output;
|
|
128
|
+
}
|
|
129
|
+
function renderAttributeValue(name, value) {
|
|
130
|
+
if (name === "class" || name === "className") {
|
|
131
|
+
const classValue = renderClassValue(value);
|
|
132
|
+
return classValue === "" ? undefined : classValue;
|
|
133
|
+
}
|
|
134
|
+
if (name === "style" && isStyleObject(value)) {
|
|
135
|
+
const styleValue = renderStyleObject(value);
|
|
136
|
+
return styleValue === "" ? undefined : styleValue;
|
|
137
|
+
}
|
|
138
|
+
if (value === true) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return String(value);
|
|
142
|
+
}
|
|
143
|
+
function normalizeAttributeName(name) {
|
|
144
|
+
if (name === "className") {
|
|
145
|
+
return "class";
|
|
146
|
+
}
|
|
147
|
+
if (name === "htmlFor") {
|
|
148
|
+
return "for";
|
|
149
|
+
}
|
|
150
|
+
return name;
|
|
151
|
+
}
|
|
152
|
+
function renderClassValue(value) {
|
|
153
|
+
if (value === false || value === null || value === undefined) {
|
|
154
|
+
return "";
|
|
155
|
+
}
|
|
156
|
+
if (Array.isArray(value)) {
|
|
157
|
+
return value.map(renderClassValue).filter(Boolean).join(" ");
|
|
158
|
+
}
|
|
159
|
+
if (typeof value === "object") {
|
|
160
|
+
return Object.entries(value)
|
|
161
|
+
.filter(([, enabled]) => Boolean(enabled))
|
|
162
|
+
.map(([name]) => name)
|
|
163
|
+
.join(" ");
|
|
164
|
+
}
|
|
165
|
+
return String(value);
|
|
166
|
+
}
|
|
167
|
+
function isStyleObject(value) {
|
|
168
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
169
|
+
}
|
|
170
|
+
function renderStyleObject(style) {
|
|
171
|
+
return Object.entries(style)
|
|
172
|
+
.filter(([, value]) => value !== false && value !== null && value !== undefined)
|
|
173
|
+
.map(([name, value]) => `${normalizeStyleName(name)}:${String(value)}`)
|
|
174
|
+
.join(";");
|
|
175
|
+
}
|
|
176
|
+
function normalizeStyleName(name) {
|
|
177
|
+
if (name.startsWith("--")) {
|
|
178
|
+
return name;
|
|
179
|
+
}
|
|
180
|
+
return name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
181
|
+
}
|
|
182
|
+
function isMarkup(value) {
|
|
183
|
+
return typeof value === "object" && value !== null && markupSymbol in value;
|
|
184
|
+
}
|
|
185
|
+
function escapeHtml(value) {
|
|
186
|
+
return value
|
|
187
|
+
.replaceAll("&", "&")
|
|
188
|
+
.replaceAll("<", "<")
|
|
189
|
+
.replaceAll(">", ">");
|
|
190
|
+
}
|
|
191
|
+
function escapeAttribute(value) {
|
|
192
|
+
return escapeHtml(value).replaceAll('"', """);
|
|
193
|
+
}
|
|
194
|
+
function islandAttrs(attrs) {
|
|
195
|
+
return Object.fromEntries(Object.entries(attrs).filter(([name]) => name !== "data-island" && name !== "data-island-key"));
|
|
196
|
+
}
|
|
197
|
+
function escapeJsonScript(value) {
|
|
198
|
+
const json = JSON.stringify(value);
|
|
199
|
+
if (json === undefined) {
|
|
200
|
+
throw new TypeError("Island props must be JSON-serializable.");
|
|
201
|
+
}
|
|
202
|
+
return json
|
|
203
|
+
.replaceAll("<", "\\u003c")
|
|
204
|
+
.replaceAll(">", "\\u003e")
|
|
205
|
+
.replaceAll("&", "\\u0026")
|
|
206
|
+
.replaceAll("\u2028", "\\u2028")
|
|
207
|
+
.replaceAll("\u2029", "\\u2029");
|
|
208
|
+
}
|
package/dist/images.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export type ImageFormat = "webp" | "avif" | "jpeg" | "png";
|
|
2
|
+
export type ImageInput = {
|
|
3
|
+
source: string;
|
|
4
|
+
publicPath: string;
|
|
5
|
+
};
|
|
6
|
+
export type GeneratedImage = {
|
|
7
|
+
source: string;
|
|
8
|
+
publicPath: string;
|
|
9
|
+
width: number;
|
|
10
|
+
format: ImageFormat;
|
|
11
|
+
bytes: number;
|
|
12
|
+
};
|
|
13
|
+
export type SkippedImage = {
|
|
14
|
+
source: string;
|
|
15
|
+
publicPath?: string;
|
|
16
|
+
width?: number;
|
|
17
|
+
format?: ImageFormat;
|
|
18
|
+
bytes?: number;
|
|
19
|
+
reason: "converter-unavailable" | "unsupported-format" | "existing-current";
|
|
20
|
+
};
|
|
21
|
+
export type ImageConverter = (input: {
|
|
22
|
+
source: string;
|
|
23
|
+
outputPath: string;
|
|
24
|
+
width: number;
|
|
25
|
+
format: ImageFormat;
|
|
26
|
+
}) => Promise<void>;
|
|
27
|
+
export type OptimizeImagesOptions = {
|
|
28
|
+
outDir: string;
|
|
29
|
+
images: ImageInput[];
|
|
30
|
+
widths: number[];
|
|
31
|
+
formats: ImageFormat[];
|
|
32
|
+
skipExisting?: boolean;
|
|
33
|
+
converter?: ImageConverter;
|
|
34
|
+
loadConverter?: () => Promise<ImageConverter | undefined>;
|
|
35
|
+
};
|
|
36
|
+
export type OptimizeImagesResult = {
|
|
37
|
+
generated: GeneratedImage[];
|
|
38
|
+
skipped: SkippedImage[];
|
|
39
|
+
};
|
|
40
|
+
export type ImageResultOrImages = OptimizeImagesResult | GeneratedImage[];
|
|
41
|
+
/**
|
|
42
|
+
* Number of distinct source images that could not be optimized because no image
|
|
43
|
+
* converter (sharp) was available. Lets callers surface an actionable warning
|
|
44
|
+
* instead of silently shipping unoptimized images.
|
|
45
|
+
*/
|
|
46
|
+
export declare function countConverterUnavailable(result: OptimizeImagesResult): number;
|
|
47
|
+
/**
|
|
48
|
+
* Resolve whether the optional `sharp` dependency can be loaded in the current
|
|
49
|
+
* environment. Returns false when sharp is not installed or its native binary
|
|
50
|
+
* is unavailable for the current platform.
|
|
51
|
+
*/
|
|
52
|
+
export declare function isSharpAvailable(loadConverter?: () => Promise<ImageConverter | undefined>): Promise<boolean>;
|
|
53
|
+
export declare function optimizeImages(options: OptimizeImagesOptions): Promise<OptimizeImagesResult>;
|
|
54
|
+
export declare function imagesForSource(resultOrImages: ImageResultOrImages, sourceOrPublicPath: string): GeneratedImage[];
|
|
55
|
+
export declare function srcsetFor(images: GeneratedImage[]): string;
|
|
56
|
+
export type PictureSource = {
|
|
57
|
+
format: ImageFormat;
|
|
58
|
+
type: `image/${ImageFormat}`;
|
|
59
|
+
srcset: string;
|
|
60
|
+
};
|
|
61
|
+
export declare function pictureSourcesFor(images: GeneratedImage[], formats?: ImageFormat[]): PictureSource[];
|
|
62
|
+
export type PictureImageLoading = "eager" | "lazy";
|
|
63
|
+
export type PictureImageDecoding = "async" | "auto" | "sync";
|
|
64
|
+
export type PictureAttrsFallback = {
|
|
65
|
+
src: string;
|
|
66
|
+
width: number;
|
|
67
|
+
height: number;
|
|
68
|
+
alt: string;
|
|
69
|
+
sizes?: string;
|
|
70
|
+
loading?: PictureImageLoading;
|
|
71
|
+
decoding?: PictureImageDecoding;
|
|
72
|
+
fallbackFormat?: ImageFormat;
|
|
73
|
+
formats?: ImageFormat[];
|
|
74
|
+
};
|
|
75
|
+
export type PictureAttrsSource = PictureSource & {
|
|
76
|
+
sizes?: string;
|
|
77
|
+
};
|
|
78
|
+
export type PictureImgAttrs = {
|
|
79
|
+
src: string;
|
|
80
|
+
srcset?: string;
|
|
81
|
+
sizes?: string;
|
|
82
|
+
width: number;
|
|
83
|
+
height: number;
|
|
84
|
+
loading?: PictureImageLoading;
|
|
85
|
+
decoding?: PictureImageDecoding;
|
|
86
|
+
alt: string;
|
|
87
|
+
};
|
|
88
|
+
export type PictureAttrs = {
|
|
89
|
+
sources: PictureAttrsSource[];
|
|
90
|
+
img: PictureImgAttrs;
|
|
91
|
+
};
|
|
92
|
+
export declare function pictureAttrsFor(images: GeneratedImage[], fallback: PictureAttrsFallback): PictureAttrs;
|
|
93
|
+
export declare function pictureHtmlFor(images: GeneratedImage[], fallback: PictureAttrsFallback): string;
|
|
94
|
+
export declare function renderPicture(attrs: PictureAttrs): string;
|
|
95
|
+
export declare function loadSharpConverter(): Promise<ImageConverter | undefined>;
|