@rebask/core 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/head.d.ts +2 -11
- package/dist/components/head.d.ts.map +1 -1
- package/dist/components/head.js +12 -24
- package/dist/components/image.d.ts +16 -3
- package/dist/components/image.d.ts.map +1 -1
- package/dist/components/image.js +50 -68
- package/dist/components/link.d.ts.map +1 -1
- package/dist/components/link.js +6 -2
- package/dist/entry.browser.js +2 -2
- package/dist/entry.ssr.d.ts.map +1 -1
- package/dist/entry.ssr.js +12 -7
- package/dist/handlers/rsc/background-render.js +2 -2
- package/dist/handlers/rsc/render-request.js +2 -2
- package/dist/handlers/rsc/render-to-response.js +2 -2
- package/dist/handlers/rsc/state.d.ts.map +1 -1
- package/dist/handlers/rsc/state.js +11 -5
- package/dist/lib/client-route-prediction.d.ts +1 -1
- package/dist/lib/client-route-prediction.d.ts.map +1 -1
- package/dist/lib/client-route-prediction.js +2 -7
- package/dist/lib/head/head-manager.d.ts +9 -0
- package/dist/lib/head/head-manager.d.ts.map +1 -0
- package/dist/lib/head/head-manager.js +98 -0
- package/dist/lib/head/head-script.js +8 -13
- package/dist/lib/image-optimization/fetch-remote-image.d.ts +17 -0
- package/dist/lib/image-optimization/fetch-remote-image.d.ts.map +1 -0
- package/dist/lib/image-optimization/fetch-remote-image.js +157 -0
- package/dist/lib/image-optimization/get-img-props.d.ts +3 -5
- package/dist/lib/image-optimization/get-img-props.d.ts.map +1 -1
- package/dist/lib/image-optimization/get-img-props.js +66 -40
- package/dist/lib/image-optimization/handler.d.ts +0 -3
- package/dist/lib/image-optimization/handler.d.ts.map +1 -1
- package/dist/lib/image-optimization/handler.js +339 -124
- package/dist/lib/image-optimization/image-config.d.ts +31 -4
- package/dist/lib/image-optimization/image-config.d.ts.map +1 -1
- package/dist/lib/image-optimization/image-config.js +6 -0
- package/dist/lib/image-optimization/image-error.d.ts +8 -0
- package/dist/lib/image-optimization/image-error.d.ts.map +1 -0
- package/dist/lib/image-optimization/image-error.js +10 -0
- package/dist/lib/image-optimization/image-plugin.d.ts +2 -0
- package/dist/lib/image-optimization/image-plugin.d.ts.map +1 -1
- package/dist/lib/image-optimization/image-plugin.js +36 -16
- package/dist/lib/image-optimization/is-private-ip.d.ts +2 -0
- package/dist/lib/image-optimization/is-private-ip.d.ts.map +1 -0
- package/dist/lib/image-optimization/is-private-ip.js +43 -0
- package/dist/lib/image-optimization/match-patterns.d.ts +4 -0
- package/dist/lib/image-optimization/match-patterns.d.ts.map +1 -0
- package/dist/lib/image-optimization/match-patterns.js +39 -0
- package/dist/lib/image-optimization/read-body.d.ts +2 -0
- package/dist/lib/image-optimization/read-body.d.ts.map +1 -0
- package/dist/lib/image-optimization/read-body.js +27 -0
- package/dist/lib/image-optimization/static-image-loader.d.ts +2 -0
- package/dist/lib/image-optimization/static-image-loader.d.ts.map +1 -0
- package/dist/lib/image-optimization/static-image-loader.js +60 -0
- package/dist/lib/plugin-names.d.ts +3 -0
- package/dist/lib/plugin-names.d.ts.map +1 -0
- package/dist/lib/plugin-names.js +2 -0
- package/dist/lib/proxy/build-proxy.d.ts +1 -1
- package/dist/lib/proxy/build-proxy.d.ts.map +1 -1
- package/dist/lib/proxy/build-proxy.js +4 -11
- package/dist/lib/proxy/pattern.d.ts +7 -0
- package/dist/lib/proxy/pattern.d.ts.map +1 -0
- package/dist/lib/proxy/pattern.js +18 -0
- package/dist/lib/proxy/types.d.ts +3 -1
- package/dist/lib/proxy/types.d.ts.map +1 -1
- package/dist/lib/route-prediction.d.ts.map +1 -1
- package/dist/lib/rsc-request.d.ts.map +1 -1
- package/dist/lib/rsc-request.js +6 -4
- package/dist/lib/server-router/build-entries.d.ts +76 -0
- package/dist/lib/server-router/build-entries.d.ts.map +1 -0
- package/dist/lib/server-router/build-entries.js +74 -0
- package/dist/lib/server-router/get-router.d.ts.map +1 -1
- package/dist/lib/server-router/get-router.js +55 -105
- package/dist/lib/server-router/rou3.d.ts +20 -0
- package/dist/lib/server-router/rou3.d.ts.map +1 -0
- package/dist/lib/server-router/rou3.js +55 -0
- package/dist/lib/server-router/route-entries.d.ts +16 -0
- package/dist/lib/server-router/route-entries.d.ts.map +1 -0
- package/dist/lib/server-router/route-entries.js +67 -0
- package/dist/lib/server-router/types.d.ts +24 -33
- package/dist/lib/server-router/types.d.ts.map +1 -1
- package/dist/lib/server-router/types.js +1 -6
- package/dist/lib/types.d.ts +2 -2
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/plugins/prerender.d.ts +3 -4
- package/dist/plugins/prerender.d.ts.map +1 -1
- package/dist/plugins/prerender.js +31 -0
- package/dist/plugins/proxy.d.ts.map +1 -1
- package/dist/plugins/proxy.js +7 -1
- package/dist/plugins/router/rou3-compiled.d.ts +9 -0
- package/dist/plugins/router/rou3-compiled.d.ts.map +1 -0
- package/dist/plugins/router/rou3-compiled.js +14 -0
- package/dist/plugins/router/rou3-router.d.ts +3 -0
- package/dist/plugins/router/rou3-router.d.ts.map +1 -0
- package/dist/plugins/router/rou3-router.js +16 -0
- package/dist/plugins/router/router-plugin.d.ts +6 -16
- package/dist/plugins/router/router-plugin.d.ts.map +1 -1
- package/dist/plugins/router/router-plugin.js +43 -22
- package/dist/proxy.d.ts +1 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +1 -0
- package/dist/request-context.d.ts +0 -2
- package/dist/request-context.d.ts.map +1 -1
- package/dist/request-context.js +0 -4
- package/dist/router.d.ts +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +4 -4
- package/dist/server-router.d.ts +3 -3
- package/dist/server-router.d.ts.map +1 -1
- package/dist/server-router.js +2 -2
- package/dist/vite.d.ts +8 -3
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +72 -33
- package/package.json +6 -4
- package/dist/components/error-boundary-client.d.ts +0 -49
- package/dist/components/error-boundary-client.d.ts.map +0 -1
- package/dist/components/error-boundary-client.js +0 -71
- package/dist/components/error-boundary.browser.d.ts +0 -8
- package/dist/components/error-boundary.browser.d.ts.map +0 -1
- package/dist/components/error-boundary.browser.js +0 -6
- package/dist/components/error.d.ts +0 -2
- package/dist/components/error.d.ts.map +0 -1
- package/dist/components/error.js +0 -5
- package/dist/components/not-found-activator.d.ts +0 -11
- package/dist/components/not-found-activator.d.ts.map +0 -1
- package/dist/components/not-found-activator.js +0 -26
- package/dist/components/not-found-boundary-client.d.ts +0 -3
- package/dist/components/not-found-boundary-client.d.ts.map +0 -1
- package/dist/components/not-found-boundary-client.js +0 -20
- package/dist/components/not-found-boundary.browser.d.ts +0 -8
- package/dist/components/not-found-boundary.browser.d.ts.map +0 -1
- package/dist/components/not-found-boundary.browser.js +0 -4
- package/dist/components/not-found-boundary.d.ts +0 -8
- package/dist/components/not-found-boundary.d.ts.map +0 -1
- package/dist/components/not-found-boundary.js +0 -9
- package/dist/components/not-found-thrower.d.ts +0 -2
- package/dist/components/not-found-thrower.d.ts.map +0 -1
- package/dist/components/not-found-thrower.js +0 -7
- package/dist/components/not-found.d.ts +0 -2
- package/dist/components/not-found.d.ts.map +0 -1
- package/dist/components/not-found.js +0 -5
- package/dist/components/status-page.d.ts +0 -6
- package/dist/components/status-page.d.ts.map +0 -1
- package/dist/components/status-page.js +0 -61
- package/dist/handler.d.ts +0 -12
- package/dist/handler.d.ts.map +0 -1
- package/dist/handler.js +0 -21
- package/dist/handlers/rsc/constants.d.ts +0 -3
- package/dist/handlers/rsc/constants.d.ts.map +0 -1
- package/dist/handlers/rsc/constants.js +0 -2
- package/dist/handlers/rsc/create-response.d.ts +0 -3
- package/dist/handlers/rsc/create-response.d.ts.map +0 -1
- package/dist/handlers/rsc/create-response.js +0 -378
- package/dist/handlers/rsc/render-with-retries.d.ts +0 -4
- package/dist/handlers/rsc/render-with-retries.d.ts.map +0 -1
- package/dist/handlers/rsc/render-with-retries.js +0 -154
- package/dist/handlers/rsc/retry-loop.d.ts +0 -3
- package/dist/handlers/rsc/retry-loop.d.ts.map +0 -1
- package/dist/handlers/rsc/retry-loop.js +0 -145
- package/dist/handlers/rsc/stream-rsc-payload.d.ts +0 -9
- package/dist/handlers/rsc/stream-rsc-payload.d.ts.map +0 -1
- package/dist/handlers/rsc/stream-rsc-payload.js +0 -20
- package/dist/handlers/rsc/types.d.ts +0 -26
- package/dist/handlers/rsc/types.d.ts.map +0 -1
- package/dist/handlers/rsc/types.js +0 -7
- package/dist/handlers/rsc.d.ts +0 -20
- package/dist/handlers/rsc.d.ts.map +0 -1
- package/dist/handlers/rsc.js +0 -872
- package/dist/integration.d.ts +0 -14
- package/dist/integration.d.ts.map +0 -1
- package/dist/integration.js +0 -9
- package/dist/lib/dev-module-load-race.d.ts +0 -8
- package/dist/lib/dev-module-load-race.d.ts.map +0 -1
- package/dist/lib/dev-module-load-race.js +0 -31
- package/dist/lib/dev-page-exports.d.ts +0 -5
- package/dist/lib/dev-page-exports.d.ts.map +0 -1
- package/dist/lib/dev-page-exports.js +0 -14
- package/dist/lib/environment-names.d.ts +0 -11
- package/dist/lib/environment-names.d.ts.map +0 -1
- package/dist/lib/environment-names.js +0 -10
- package/dist/lib/initial-hash-scroll.d.ts.map +0 -1
- package/dist/lib/not-found.d.ts +0 -5
- package/dist/lib/not-found.d.ts.map +0 -1
- package/dist/lib/not-found.js +0 -7
- package/dist/lib/page-exports.d.ts +0 -9
- package/dist/lib/page-exports.d.ts.map +0 -1
- package/dist/lib/page-exports.js +0 -47
- package/dist/lib/pattern-segments.d.ts +0 -8
- package/dist/lib/pattern-segments.d.ts.map +0 -1
- package/dist/lib/pattern-segments.js +0 -22
- package/dist/lib/ppr/cache-status.d.ts +0 -3
- package/dist/lib/ppr/cache-status.d.ts.map +0 -1
- package/dist/lib/ppr/cache-status.js +0 -5
- package/dist/lib/server-router-contract.d.ts +0 -42
- package/dist/lib/server-router-contract.d.ts.map +0 -1
- package/dist/lib/server-router-contract.js +0 -6
- package/dist/lib/server-router-loader.d.ts +0 -10
- package/dist/lib/server-router-loader.d.ts.map +0 -1
- package/dist/lib/server-router-loader.js +0 -180
- package/dist/lib/server-router.d.ts +0 -8
- package/dist/lib/server-router.d.ts.map +0 -1
- package/dist/lib/server-router.js +0 -173
- package/dist/plugins/ppr-upgrade.d.ts +0 -36
- package/dist/plugins/ppr-upgrade.d.ts.map +0 -1
- package/dist/plugins/ppr-upgrade.js +0 -32
- package/dist/plugins/server-router-fallback.d.ts +0 -15
- package/dist/plugins/server-router-fallback.d.ts.map +0 -1
- package/dist/plugins/server-router-fallback.js +0 -30
- package/dist/server.d.ts +0 -10
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -57
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { createPortal, flushSync } from "react-dom";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
const heads = new Map();
|
|
5
|
+
let root = null;
|
|
6
|
+
let titleElement = null;
|
|
7
|
+
let scheduled = false;
|
|
8
|
+
function getText(children) {
|
|
9
|
+
if (typeof children === "string" ||
|
|
10
|
+
typeof children === "number" ||
|
|
11
|
+
typeof children === "bigint") {
|
|
12
|
+
return String(children);
|
|
13
|
+
}
|
|
14
|
+
if (Array.isArray(children))
|
|
15
|
+
return children.map(getText).join("");
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
function updateTitle(entry) {
|
|
19
|
+
if (!entry) {
|
|
20
|
+
titleElement?.remove();
|
|
21
|
+
titleElement = null;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!titleElement)
|
|
25
|
+
titleElement = document.createElement("title");
|
|
26
|
+
const firstTitle = document.head.querySelector("title");
|
|
27
|
+
if (firstTitle !== titleElement)
|
|
28
|
+
document.head.insertBefore(titleElement, firstTitle);
|
|
29
|
+
titleElement.textContent = getText(entry.children);
|
|
30
|
+
}
|
|
31
|
+
function updateHead() {
|
|
32
|
+
scheduled = false;
|
|
33
|
+
if (!root) {
|
|
34
|
+
for (const element of document.querySelectorAll("[data-rebask-head]")) {
|
|
35
|
+
element.remove();
|
|
36
|
+
}
|
|
37
|
+
new MutationObserver((mutations) => {
|
|
38
|
+
for (const mutation of mutations) {
|
|
39
|
+
for (const node of mutation.addedNodes) {
|
|
40
|
+
if (node.nodeType !== 1)
|
|
41
|
+
continue;
|
|
42
|
+
const element = node;
|
|
43
|
+
if (element.matches("[data-rebask-head]"))
|
|
44
|
+
element.remove();
|
|
45
|
+
else
|
|
46
|
+
for (const child of element.querySelectorAll("[data-rebask-head]"))
|
|
47
|
+
child.remove();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}).observe(document.documentElement, { childList: true, subtree: true });
|
|
51
|
+
root = createRoot(document.createElement("div"));
|
|
52
|
+
}
|
|
53
|
+
const keyed = new Map();
|
|
54
|
+
const elements = [];
|
|
55
|
+
let title;
|
|
56
|
+
for (const [id, entries] of heads) {
|
|
57
|
+
for (let i = 0; i < entries.length; i++) {
|
|
58
|
+
const entry = entries[i];
|
|
59
|
+
let key = entry.tag.toLowerCase();
|
|
60
|
+
if (key === "meta") {
|
|
61
|
+
const attr = entry.props.name || entry.props.property || entry.props.httpEquiv;
|
|
62
|
+
key = attr ? `${key}:${String(attr)}` : null;
|
|
63
|
+
}
|
|
64
|
+
else if (key === "link") {
|
|
65
|
+
key = entry.props.rel === "canonical" ? "link:canonical" : null;
|
|
66
|
+
}
|
|
67
|
+
if (key === null) {
|
|
68
|
+
elements.push(createElement(entry.tag, { key: `${id}:${i}`, ...entry.props }, entry.children));
|
|
69
|
+
}
|
|
70
|
+
else if (key === "title") {
|
|
71
|
+
title = entry;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
keyed.set(key, entry);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const [key, entry] of keyed) {
|
|
79
|
+
elements.push(createElement(entry.tag, { key, ...entry.props }, entry.children));
|
|
80
|
+
}
|
|
81
|
+
const headRoot = root;
|
|
82
|
+
flushSync(() => headRoot.render(createPortal(elements, document.head)));
|
|
83
|
+
updateTitle(title);
|
|
84
|
+
}
|
|
85
|
+
function scheduleUpdate() {
|
|
86
|
+
if (scheduled)
|
|
87
|
+
return;
|
|
88
|
+
scheduled = true;
|
|
89
|
+
queueMicrotask(updateHead);
|
|
90
|
+
}
|
|
91
|
+
export function registerHead(id, entries) {
|
|
92
|
+
heads.set(id, entries);
|
|
93
|
+
scheduleUpdate();
|
|
94
|
+
}
|
|
95
|
+
export function unregisterHead(id) {
|
|
96
|
+
if (heads.delete(id))
|
|
97
|
+
scheduleUpdate();
|
|
98
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// This script is inlined in the
|
|
1
|
+
// This script is inlined in the document bootstrap.
|
|
2
2
|
function dedupeHeadElements() {
|
|
3
|
-
const seen = new
|
|
4
|
-
|
|
3
|
+
const seen = new Set();
|
|
4
|
+
// Keep the last element per key.
|
|
5
|
+
for (const element of Array.from(document.querySelectorAll("[data-rebask-head]")).reverse()) {
|
|
5
6
|
let key = element.tagName.toLowerCase();
|
|
6
7
|
if (key === "meta") {
|
|
7
8
|
const attr = element.getAttribute("name") ||
|
|
@@ -16,23 +17,17 @@ function dedupeHeadElements() {
|
|
|
16
17
|
continue;
|
|
17
18
|
key = "link:canonical";
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
const previous = seen.get(key);
|
|
21
|
-
if (!previous || order > previous.order) {
|
|
22
|
-
if (previous)
|
|
23
|
-
previous.element.remove();
|
|
24
|
-
seen.set(key, { element, order });
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
20
|
+
if (seen.has(key))
|
|
27
21
|
element.remove();
|
|
28
|
-
|
|
22
|
+
else
|
|
23
|
+
seen.add(key);
|
|
29
24
|
}
|
|
30
25
|
}
|
|
31
26
|
dedupeHeadElements();
|
|
32
27
|
const observer = new MutationObserver((mutations) => {
|
|
33
28
|
for (const mutation of mutations) {
|
|
34
29
|
for (const node of mutation.addedNodes) {
|
|
35
|
-
if (node.nodeType === 1 && node.matches?.("[data-
|
|
30
|
+
if (node.nodeType === 1 && node.matches?.("[data-rebask-head]")) {
|
|
36
31
|
dedupeHeadElements();
|
|
37
32
|
return;
|
|
38
33
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ImageConfigComplete } from "./image-config.js";
|
|
2
|
+
type RemoteImageConfig = Pick<ImageConfigComplete, "remotePatterns" | "dangerouslyAllowLocalIP" | "maximumRedirects" | "maximumResponseBody">;
|
|
3
|
+
export type RemoteImageCacheControl = {
|
|
4
|
+
maxAge: number | undefined;
|
|
5
|
+
browserMaxAge: number | undefined;
|
|
6
|
+
sharedMaxAge: number | undefined;
|
|
7
|
+
noStore: boolean;
|
|
8
|
+
private: boolean;
|
|
9
|
+
noCache: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare const PUBLIC_IMAGE_CACHE_CONTROL: RemoteImageCacheControl;
|
|
12
|
+
export declare function fetchRemoteImage(href: string, config: RemoteImageConfig): Promise<{
|
|
13
|
+
buffer: Buffer;
|
|
14
|
+
cacheControl: RemoteImageCacheControl;
|
|
15
|
+
}>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=fetch-remote-image.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-remote-image.d.ts","sourceRoot":"","sources":["../../../src/lib/image-optimization/fetch-remote-image.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAS5D,KAAK,iBAAiB,GAAG,IAAI,CAC5B,mBAAmB,EACnB,gBAAgB,GAAG,yBAAyB,GAAG,kBAAkB,GAAG,qBAAqB,CACzF,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACrC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,eAAO,MAAM,0BAA0B,EAAE,uBAOxC,CAAA;AAwGD,wBAAsB,gBAAgB,CACrC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,iBAAiB,GACvB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,uBAAuB,CAAA;CAAE,CAAC,CAiEpE"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { lookup } from "node:dns/promises";
|
|
2
|
+
import { request as httpRequest } from "node:http";
|
|
3
|
+
import { request as httpsRequest } from "node:https";
|
|
4
|
+
import { isIP } from "node:net";
|
|
5
|
+
import { ImageError, INVALID_UPSTREAM, NOT_ALLOWED, TIMED_OUT } from "./image-error.js";
|
|
6
|
+
import { isPrivateIp } from "./is-private-ip.js";
|
|
7
|
+
import { hasRemoteMatch } from "./match-patterns.js";
|
|
8
|
+
import { readBody } from "./read-body.js";
|
|
9
|
+
const TIMEOUT_MS = 7000;
|
|
10
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
11
|
+
export const PUBLIC_IMAGE_CACHE_CONTROL = {
|
|
12
|
+
maxAge: undefined,
|
|
13
|
+
browserMaxAge: undefined,
|
|
14
|
+
sharedMaxAge: undefined,
|
|
15
|
+
noStore: false,
|
|
16
|
+
private: false,
|
|
17
|
+
noCache: false,
|
|
18
|
+
};
|
|
19
|
+
function parseCacheControl(cacheControl) {
|
|
20
|
+
let maxAge;
|
|
21
|
+
let sMaxAge;
|
|
22
|
+
let noStore = false;
|
|
23
|
+
let privateCache = false;
|
|
24
|
+
let noCache = false;
|
|
25
|
+
for (const directive of cacheControl?.split(",") ?? []) {
|
|
26
|
+
const [rawKey, rawValue] = directive.trim().split("=", 2);
|
|
27
|
+
if (!rawKey)
|
|
28
|
+
continue;
|
|
29
|
+
const key = rawKey.trim().toLowerCase();
|
|
30
|
+
const parsed = rawValue ? parseInt(rawValue.replace(/"/g, "").trim(), 10) : Number.NaN;
|
|
31
|
+
const validAge = !Number.isNaN(parsed);
|
|
32
|
+
if (key === "max-age" && validAge) {
|
|
33
|
+
maxAge = parsed;
|
|
34
|
+
}
|
|
35
|
+
else if (key === "s-maxage" && validAge) {
|
|
36
|
+
sMaxAge = parsed;
|
|
37
|
+
}
|
|
38
|
+
else if (key === "no-store") {
|
|
39
|
+
noStore = true;
|
|
40
|
+
}
|
|
41
|
+
else if (key === "private") {
|
|
42
|
+
privateCache = true;
|
|
43
|
+
}
|
|
44
|
+
else if (key === "no-cache") {
|
|
45
|
+
noCache = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
maxAge: sMaxAge ?? maxAge,
|
|
50
|
+
browserMaxAge: maxAge,
|
|
51
|
+
sharedMaxAge: sMaxAge,
|
|
52
|
+
noStore,
|
|
53
|
+
private: privateCache,
|
|
54
|
+
noCache,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function resolveAddresses(hostname) {
|
|
58
|
+
const literalFamily = isIP(hostname);
|
|
59
|
+
if (literalFamily) {
|
|
60
|
+
return [{ address: hostname, family: literalFamily }];
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
return await lookup(hostname, { all: true });
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
throw new ImageError(400, INVALID_UPSTREAM);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Pin the connection to the addresses checked above so a second DNS lookup cannot rebind.
|
|
70
|
+
function createPinnedLookup(hostname, addresses) {
|
|
71
|
+
return (name, options, callback) => {
|
|
72
|
+
if (name !== hostname) {
|
|
73
|
+
const error = new Error(`Unexpected lookup for ${name}`);
|
|
74
|
+
error.code = "ENOTFOUND";
|
|
75
|
+
callback(error, [], 0);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const family = typeof options.family === "number" ? options.family : 0;
|
|
79
|
+
const matched = family ? addresses.filter((address) => address.family === family) : addresses;
|
|
80
|
+
if (matched.length === 0) {
|
|
81
|
+
const error = new Error(`No address available for ${hostname}`);
|
|
82
|
+
error.code = "ENOTFOUND";
|
|
83
|
+
callback(error, [], 0);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (options.all) {
|
|
87
|
+
callback(null, matched);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
callback(null, matched[0].address, matched[0].family);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function requestImage(url, hostname, addresses, signal) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
const request = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
97
|
+
const req = request(url, {
|
|
98
|
+
lookup: isIP(hostname) ? undefined : createPinnedLookup(hostname, addresses),
|
|
99
|
+
signal,
|
|
100
|
+
headers: { accept: "*/*", "accept-encoding": "identity", "user-agent": "rebask" },
|
|
101
|
+
}, resolve);
|
|
102
|
+
req.on("error", reject);
|
|
103
|
+
req.end();
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
export async function fetchRemoteImage(href, config) {
|
|
107
|
+
let current = new URL(href);
|
|
108
|
+
for (let redirects = 0; redirects <= config.maximumRedirects; redirects++) {
|
|
109
|
+
if (!hasRemoteMatch(config.remotePatterns, current)) {
|
|
110
|
+
throw new ImageError(400, NOT_ALLOWED);
|
|
111
|
+
}
|
|
112
|
+
const hostname = current.hostname.replace(/^\[|\]$/g, "");
|
|
113
|
+
const addresses = await resolveAddresses(hostname);
|
|
114
|
+
if (!config.dangerouslyAllowLocalIP &&
|
|
115
|
+
addresses.some((address) => isPrivateIp(address.address))) {
|
|
116
|
+
throw new ImageError(400, NOT_ALLOWED);
|
|
117
|
+
}
|
|
118
|
+
const signal = AbortSignal.timeout(TIMEOUT_MS);
|
|
119
|
+
let response;
|
|
120
|
+
try {
|
|
121
|
+
response = await requestImage(current, hostname, addresses, signal);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
if (signal.aborted) {
|
|
125
|
+
throw new ImageError(504, TIMED_OUT);
|
|
126
|
+
}
|
|
127
|
+
throw new ImageError(400, INVALID_UPSTREAM);
|
|
128
|
+
}
|
|
129
|
+
const statusCode = response.statusCode ?? 0;
|
|
130
|
+
const location = response.headers.location;
|
|
131
|
+
if (REDIRECT_STATUSES.has(statusCode) && location) {
|
|
132
|
+
response.destroy();
|
|
133
|
+
if (redirects === config.maximumRedirects) {
|
|
134
|
+
throw new ImageError(508, INVALID_UPSTREAM);
|
|
135
|
+
}
|
|
136
|
+
let next;
|
|
137
|
+
try {
|
|
138
|
+
next = new URL(location, current);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
throw new ImageError(400, INVALID_UPSTREAM);
|
|
142
|
+
}
|
|
143
|
+
if (next.protocol !== "http:" && next.protocol !== "https:") {
|
|
144
|
+
throw new ImageError(400, NOT_ALLOWED);
|
|
145
|
+
}
|
|
146
|
+
current = next;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
150
|
+
response.destroy();
|
|
151
|
+
throw new ImageError(400, INVALID_UPSTREAM);
|
|
152
|
+
}
|
|
153
|
+
const buffer = await readBody(response, config.maximumResponseBody, signal);
|
|
154
|
+
return { buffer, cacheControl: parseCacheControl(response.headers["cache-control"]) };
|
|
155
|
+
}
|
|
156
|
+
throw new ImageError(508, INVALID_UPSTREAM);
|
|
157
|
+
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import type { CSSProperties, JSX } from "react";
|
|
2
|
-
import type { ImageConfigComplete, ImageLoaderProps } from "./image-config.js";
|
|
2
|
+
import type { ImageBlur, ImageConfigComplete, ImageLoaderProps } from "./image-config.js";
|
|
3
3
|
export interface StaticImageData {
|
|
4
4
|
src: string;
|
|
5
5
|
height: number;
|
|
6
6
|
width: number;
|
|
7
|
-
|
|
8
|
-
blurWidth?: number;
|
|
9
|
-
blurHeight?: number;
|
|
7
|
+
blur?: ImageBlur;
|
|
10
8
|
}
|
|
11
9
|
export interface StaticRequire {
|
|
12
10
|
default: StaticImageData;
|
|
@@ -44,7 +42,7 @@ export type PlaceholderStyle = Partial<Pick<CSSProperties, "backgroundSize" | "b
|
|
|
44
42
|
/**
|
|
45
43
|
* A shared function, used on both client and server, to generate the props for <img>.
|
|
46
44
|
*/
|
|
47
|
-
export declare function getImgProps({ src, sizes, unoptimized, preload, loading, className, quality, width, height, fill, style,
|
|
45
|
+
export declare function getImgProps({ src, sizes, unoptimized, preload, loading, className, quality, width, height, fill, style, loader: loaderProp, placeholder, blurDataURL, fetchPriority, decoding, ...rest }: ImageProps, _state: {
|
|
48
46
|
imgConf: ImageConfigComplete;
|
|
49
47
|
showAltText?: boolean;
|
|
50
48
|
blurComplete?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-img-props.d.ts","sourceRoot":"","sources":["../../../src/lib/image-optimization/get-img-props.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAE/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"get-img-props.d.ts","sourceRoot":"","sources":["../../../src/lib/image-optimization/get-img-props.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,9 @@ import { warnOnce } from "./warn-once.js";
|
|
|
5
5
|
const VALID_LOADING_VALUES = ["lazy", "eager", undefined];
|
|
6
6
|
// Object-fit values that are not valid background-size values
|
|
7
7
|
const INVALID_BACKGROUND_SIZE_VALUES = ["-moz-initial", "fill", "none", "scale-down", undefined];
|
|
8
|
+
const allImgs = new Map();
|
|
9
|
+
let perfObserver;
|
|
10
|
+
const preparedConfigs = new WeakMap();
|
|
8
11
|
function isStaticRequire(src) {
|
|
9
12
|
return src.default !== undefined;
|
|
10
13
|
}
|
|
@@ -33,7 +36,8 @@ function getWidths({ deviceSizes, allSizes }, width, sizes) {
|
|
|
33
36
|
// Find all the "vw" percent sizes used in the sizes prop
|
|
34
37
|
const viewportWidthRe = /(^|\s)(1?\d?\d)vw/g;
|
|
35
38
|
const percentSizes = [];
|
|
36
|
-
|
|
39
|
+
let match;
|
|
40
|
+
while ((match = viewportWidthRe.exec(sizes))) {
|
|
37
41
|
percentSizes.push(parseInt(match[2]));
|
|
38
42
|
}
|
|
39
43
|
if (percentSizes.length) {
|
|
@@ -58,14 +62,14 @@ function getWidths({ deviceSizes, allSizes }, width, sizes) {
|
|
|
58
62
|
// > wasteful as the human eye cannot see that level of detail without
|
|
59
63
|
// > something like a magnifying glass.
|
|
60
64
|
// https://blog.twitter.com/engineering/en_us/topics/infrastructure/2019/capping-image-fidelity-on-ultra-high-resolution-devices.html
|
|
61
|
-
[width, width * 2
|
|
65
|
+
[width, width * 2].map((w) => allSizes.find((p) => p >= w) || allSizes[allSizes.length - 1])),
|
|
62
66
|
];
|
|
63
67
|
return { widths, kind: "x" };
|
|
64
68
|
}
|
|
65
69
|
/**
|
|
66
70
|
* A shared function, used on both client and server, to generate the props for <img>.
|
|
67
71
|
*/
|
|
68
|
-
export function getImgProps({ src, sizes, unoptimized = false, preload = false, loading, className, quality, width, height, fill = false, style,
|
|
72
|
+
export function getImgProps({ src, sizes, unoptimized = false, preload = false, loading, className, quality, width, height, fill = false, style, loader: loaderProp, placeholder = "empty", blurDataURL, fetchPriority, decoding = "async", ...rest }, _state) {
|
|
69
73
|
const { imgConf, showAltText, blurComplete } = _state;
|
|
70
74
|
let config;
|
|
71
75
|
const c = imgConf || imageConfigDefault;
|
|
@@ -73,19 +77,22 @@ export function getImgProps({ src, sizes, unoptimized = false, preload = false,
|
|
|
73
77
|
config = c;
|
|
74
78
|
}
|
|
75
79
|
else {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
const prepared = preparedConfigs.get(c);
|
|
81
|
+
if (prepared) {
|
|
82
|
+
config = prepared;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const allSizes = [...c.deviceSizes, ...c.imageSizes].sort((a, b) => a - b);
|
|
86
|
+
const deviceSizes = [...c.deviceSizes].sort((a, b) => a - b);
|
|
87
|
+
const qualities = c.qualities ? [...c.qualities].sort((a, b) => a - b) : undefined;
|
|
88
|
+
config = { ...c, allSizes, deviceSizes, qualities };
|
|
89
|
+
preparedConfigs.set(c, config);
|
|
90
|
+
}
|
|
80
91
|
}
|
|
81
|
-
const loader =
|
|
82
|
-
// Remove property so it's not spread on <img> element
|
|
83
|
-
delete rest.loader;
|
|
84
|
-
delete rest.srcSet;
|
|
92
|
+
const loader = loaderProp || defaultLoader;
|
|
85
93
|
// This special value indicates that the user
|
|
86
94
|
// didn't define a "loader" prop.
|
|
87
95
|
const isDefaultLoader = "__rebask_img_default" in loader;
|
|
88
|
-
let staticSrc = "";
|
|
89
96
|
let widthInt = getInt(width);
|
|
90
97
|
let heightInt = getInt(height);
|
|
91
98
|
let blurWidth;
|
|
@@ -98,10 +105,10 @@ export function getImgProps({ src, sizes, unoptimized = false, preload = false,
|
|
|
98
105
|
if (!staticImageData.height || !staticImageData.width) {
|
|
99
106
|
throw new Error(`An object should only be passed to the image component src parameter if it comes from a static image import. It must include height and width. Received ${JSON.stringify(staticImageData)}`);
|
|
100
107
|
}
|
|
101
|
-
blurWidth = staticImageData.
|
|
102
|
-
blurHeight = staticImageData.
|
|
103
|
-
blurDataURL = blurDataURL || staticImageData.
|
|
104
|
-
|
|
108
|
+
blurWidth = staticImageData.blur?.width;
|
|
109
|
+
blurHeight = staticImageData.blur?.height;
|
|
110
|
+
blurDataURL = blurDataURL || staticImageData.blur?.dataURL;
|
|
111
|
+
src = staticImageData.src;
|
|
105
112
|
if (!fill) {
|
|
106
113
|
if (!widthInt && !heightInt) {
|
|
107
114
|
widthInt = staticImageData.width;
|
|
@@ -117,7 +124,6 @@ export function getImgProps({ src, sizes, unoptimized = false, preload = false,
|
|
|
117
124
|
}
|
|
118
125
|
}
|
|
119
126
|
}
|
|
120
|
-
src = typeof src === "string" ? src : staticSrc;
|
|
121
127
|
let isLazy = !preload && (loading === "lazy" || typeof loading === "undefined");
|
|
122
128
|
if (!src || src.startsWith("data:") || src.startsWith("blob:")) {
|
|
123
129
|
// https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
|
|
@@ -133,13 +139,7 @@ export function getImgProps({ src, sizes, unoptimized = false, preload = false,
|
|
|
133
139
|
}
|
|
134
140
|
const qualityInt = getInt(quality);
|
|
135
141
|
if (import.meta.env.DEV) {
|
|
136
|
-
if (
|
|
137
|
-
// React doesn't show the stack trace and there's
|
|
138
|
-
// no `src` to help identify which image, so we
|
|
139
|
-
// instead console.error(ref) during mount.
|
|
140
|
-
unoptimized = true;
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
142
|
+
if (src) {
|
|
143
143
|
if (fill) {
|
|
144
144
|
if (width) {
|
|
145
145
|
throw new Error(`Image with src "${src}" has both "width" and "fill" properties. Only one should be used.`);
|
|
@@ -170,12 +170,10 @@ export function getImgProps({ src, sizes, unoptimized = false, preload = false,
|
|
|
170
170
|
else if (isNaN(heightInt)) {
|
|
171
171
|
throw new Error(`Image with src "${src}" has invalid "height" property. Expected a numeric value in pixels but received "${height}".`);
|
|
172
172
|
}
|
|
173
|
-
|
|
174
|
-
if (startsWithSpaceOrControl) {
|
|
173
|
+
if (src.length > 0 && src.charCodeAt(0) <= 0x20) {
|
|
175
174
|
throw new Error(`Image with src "${src}" cannot start with a space or control character. Use src.trimStart() to remove it or encodeURIComponent(src) to keep it.`);
|
|
176
175
|
}
|
|
177
|
-
|
|
178
|
-
if (endsWithSpaceOrControl) {
|
|
176
|
+
if (src.length > 0 && src.charCodeAt(src.length - 1) <= 0x20) {
|
|
179
177
|
throw new Error(`Image with src "${src}" cannot end with a space or control character. Use src.trimEnd() to remove it or encodeURIComponent(src) to keep it.`);
|
|
180
178
|
}
|
|
181
179
|
}
|
|
@@ -191,12 +189,10 @@ export function getImgProps({ src, sizes, unoptimized = false, preload = false,
|
|
|
191
189
|
!placeholder.startsWith("data:image/")) {
|
|
192
190
|
throw new Error(`Image with src "${src}" has invalid "placeholder" property "${placeholder}".`);
|
|
193
191
|
}
|
|
194
|
-
if (placeholder !== "empty") {
|
|
195
|
-
|
|
196
|
-
warnOnce(`Image with src "${src}" is smaller than 40x40. Consider removing the "placeholder" property to improve performance.`);
|
|
197
|
-
}
|
|
192
|
+
if (placeholder !== "empty" && widthInt && heightInt && widthInt * heightInt < 1600) {
|
|
193
|
+
warnOnce(`Image with src "${src}" is smaller than 40x40. Consider removing the "placeholder" property to improve performance.`);
|
|
198
194
|
}
|
|
199
|
-
if (qualityInt && config.qualities && !config.qualities.includes(qualityInt)) {
|
|
195
|
+
if (qualityInt && config.qualities?.length && !config.qualities.includes(qualityInt)) {
|
|
200
196
|
warnOnce(`Image with src "${src}" is using quality "${qualityInt}" which is not configured in images.qualities [${config.qualities.join(", ")}]. Please update your config to [${[...config.qualities, qualityInt].sort().join(", ")}].`);
|
|
201
197
|
}
|
|
202
198
|
if (placeholder === "blur" && !blurDataURL) {
|
|
@@ -205,6 +201,7 @@ export function getImgProps({ src, sizes, unoptimized = false, preload = false,
|
|
|
205
201
|
Possible solutions:
|
|
206
202
|
- Add a "blurDataURL" property, the contents should be a small Data URL to represent the image
|
|
207
203
|
- Change the "src" property to a static import with one of the supported file types: ${VALID_BLUR_EXT.join(",")} (animated images not supported)
|
|
204
|
+
- Install \`sharp\` so Rebask can generate the blur placeholder for static imports
|
|
208
205
|
- Remove the "placeholder" property, effectively no blur effect`);
|
|
209
206
|
}
|
|
210
207
|
}
|
|
@@ -219,6 +216,7 @@ export function getImgProps({ src, sizes, unoptimized = false, preload = false,
|
|
|
219
216
|
bottom: 0,
|
|
220
217
|
}
|
|
221
218
|
: {}, showAltText ? {} : { color: "transparent" }, style);
|
|
219
|
+
const objectFit = imgStyle.objectFit || "cover";
|
|
222
220
|
const backgroundImage = !blurComplete && placeholder !== "empty"
|
|
223
221
|
? placeholder === "blur"
|
|
224
222
|
? `url("data:image/svg+xml;charset=utf-8,${getImageBlurSvg({
|
|
@@ -227,11 +225,10 @@ export function getImgProps({ src, sizes, unoptimized = false, preload = false,
|
|
|
227
225
|
blurWidth,
|
|
228
226
|
blurHeight,
|
|
229
227
|
blurDataURL: blurDataURL || "", // assume not undefined
|
|
230
|
-
objectFit
|
|
228
|
+
objectFit,
|
|
231
229
|
})}")`
|
|
232
230
|
: `url("${placeholder}")` // assume `data:image/`
|
|
233
231
|
: null;
|
|
234
|
-
const objectFit = imgStyle.objectFit || "cover";
|
|
235
232
|
const backgroundSize = !INVALID_BACKGROUND_SIZE_VALUES.includes(objectFit)
|
|
236
233
|
? objectFit
|
|
237
234
|
: objectFit === "fill"
|
|
@@ -265,18 +262,16 @@ export function getImgProps({ src, sizes, unoptimized = false, preload = false,
|
|
|
265
262
|
}
|
|
266
263
|
else {
|
|
267
264
|
const { widths, kind } = getWidths(config, widthInt, sizes);
|
|
268
|
-
const
|
|
265
|
+
const urls = widths.map((width) => loader({ config, src, quality: qualityInt, width }));
|
|
269
266
|
imgSizes = !sizes && kind === "w" ? "100vw" : sizes;
|
|
270
|
-
imgSrcSet = widths
|
|
271
|
-
.map((w, i) => `${loader({ config, src, quality: qualityInt, width: w })} ${kind === "w" ? w : i + 1}${kind}`)
|
|
272
|
-
.join(", ");
|
|
267
|
+
imgSrcSet = urls.map((url, i) => `${url} ${kind === "w" ? widths[i] : i + 1}${kind}`).join(", ");
|
|
273
268
|
// It's intended to keep `src` the last attribute because React updates
|
|
274
269
|
// attributes in order. If we keep `src` the first one, Safari will
|
|
275
270
|
// immediately start to fetch `src`, before `sizes` and `srcSet` are even
|
|
276
271
|
// updated by React. That causes multiple unnecessary requests if `srcSet`
|
|
277
272
|
// and `sizes` are defined.
|
|
278
273
|
// This bug cannot be reproduced in Chrome or Firefox.
|
|
279
|
-
imgSrc =
|
|
274
|
+
imgSrc = urls[urls.length - 1];
|
|
280
275
|
}
|
|
281
276
|
const loadingFinal = isLazy ? "lazy" : loading;
|
|
282
277
|
const props = {
|
|
@@ -293,5 +288,36 @@ export function getImgProps({ src, sizes, unoptimized = false, preload = false,
|
|
|
293
288
|
src: imgSrc,
|
|
294
289
|
};
|
|
295
290
|
const meta = { unoptimized, preload, placeholder, fill };
|
|
291
|
+
if (import.meta.env.DEV && typeof window !== "undefined") {
|
|
292
|
+
if (!perfObserver && window.PerformanceObserver) {
|
|
293
|
+
perfObserver = new PerformanceObserver((entryList) => {
|
|
294
|
+
for (const entry of entryList.getEntries()) {
|
|
295
|
+
const imgSrc = entry.element?.src ?? "";
|
|
296
|
+
const image = allImgs.get(imgSrc);
|
|
297
|
+
if (image &&
|
|
298
|
+
image.loading === "lazy" &&
|
|
299
|
+
image.placeholder === "empty" &&
|
|
300
|
+
!image.src.startsWith("data:") &&
|
|
301
|
+
!image.src.startsWith("blob:")) {
|
|
302
|
+
warnOnce(`Image with src "${image.src}" was detected as the Largest Contentful Paint (LCP). Please add the \`loading="eager"\` property if this image is above the fold.`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
try {
|
|
307
|
+
perfObserver.observe({ type: "largest-contentful-paint", buffered: true });
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
console.error(error);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
let fullUrl;
|
|
314
|
+
try {
|
|
315
|
+
fullUrl = new URL(props.src);
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
fullUrl = new URL(props.src, window.location.href);
|
|
319
|
+
}
|
|
320
|
+
allImgs.set(fullUrl.href, { src, loading: loadingFinal, placeholder });
|
|
321
|
+
}
|
|
296
322
|
return { props, meta };
|
|
297
323
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/lib/image-optimization/handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/lib/image-optimization/handler.ts"],"names":[],"mappings":"AA+TA,wBAAsB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CA6LjF"}
|