Comparing version 3.2.0 to 3.3.0
@@ -5,1 +5,2 @@ export * from "./src/types.js"; | ||
export * from "./src/parse.js"; | ||
export * from "./src/canonical.js"; |
@@ -7,3 +7,6 @@ import domains from "../data/domains.js"; | ||
export function getImageCdnForUrl(url) { | ||
const { hostname, pathname } = new URL(url); | ||
return getImageCdnForUrlByDomain(url) || getImageCdnForUrlByPath(url); | ||
} | ||
export function getImageCdnForUrlByDomain(url) { | ||
const { hostname } = new URL(url); | ||
if (cdnDomains.has(hostname)) { | ||
@@ -17,2 +20,6 @@ return cdnDomains.get(hostname); | ||
} | ||
return false; | ||
} | ||
export function getImageCdnForUrlByPath(url) { | ||
const { pathname } = new URL(url); | ||
for (const [prefix, cdn] of Object.entries(paths)) { | ||
@@ -19,0 +26,0 @@ if (pathname.startsWith(prefix)) { |
@@ -16,2 +16,3 @@ import { getImageCdnForUrl } from "./detect.js"; | ||
import { transform as keycdn } from "./transformers/keycdn.js"; | ||
import { getCanonicalCdnForUrl } from "./canonical.js"; | ||
export const getTransformer = (cdn) => ({ | ||
@@ -34,6 +35,2 @@ imgix, | ||
/** | ||
* Returns a transformer function if the given URL is from a known image CDN | ||
*/ | ||
export const getTransformerForUrl = (url) => getTransformerForCdn(getImageCdnForUrl(url)); | ||
/** | ||
* Returns a transformer function if the given CDN is supported | ||
@@ -52,6 +49,21 @@ */ | ||
export const transformUrl = (options) => { | ||
if (options.cdn) { | ||
return getTransformerForCdn(options.cdn)?.(options); | ||
const cdn = options?.cdn ?? getImageCdnForUrl(options.url); | ||
// Default to recursive | ||
if (!(options.recursive ?? true)) { | ||
return getTransformerForCdn(cdn)?.(options); | ||
} | ||
return getTransformerForUrl(options.url)?.(options); | ||
const canonical = getCanonicalCdnForUrl(options.url, cdn); | ||
if (!canonical || !canonical.cdn) { | ||
return undefined; | ||
} | ||
return getTransformer(canonical.cdn)?.({ | ||
...options, | ||
url: canonical.url, | ||
}); | ||
}; | ||
/** | ||
* Returns a transformer function if the given URL is from a known image CDN | ||
* | ||
* @deprecated Use `getCanonicalCdnForUrl` and `getTransformerForCdn` instead | ||
*/ | ||
export const getTransformerForUrl = (url) => getTransformerForCdn(getImageCdnForUrl(url)); |
@@ -1,2 +0,2 @@ | ||
import { getNumericParam, setParamIfDefined } from "../utils.js"; | ||
import { getNumericParam, setParamIfDefined, setParamIfUndefined, } from "../utils.js"; | ||
export const parse = (url) => { | ||
@@ -23,3 +23,4 @@ const parsedUrl = new URL(url); | ||
setParamIfDefined(url, "quality", getNumericParam(url, "quality"), true); | ||
setParamIfUndefined(url, "enlarge", 0); | ||
return url; | ||
}; |
@@ -0,3 +1,4 @@ | ||
export { delegateUrl } from "./vercel.js"; | ||
import { parse as vercelParse, transform as vercelTransform, } from "./vercel.js"; | ||
export const parse = (url) => ({ ...vercelParse(url), cdn: "nextjs" }); | ||
export const transform = (params) => vercelTransform({ ...params, cdn: "nextjs" }); |
import { setParamIfDefined, setParamIfUndefined, toRelativeUrl, } from "../utils.js"; | ||
import { getTransformerForCdn } from "../transform.js"; | ||
import { getImageCdnForUrl } from "../detect.js"; | ||
import { getImageCdnForUrlByDomain } from "../detect.js"; | ||
export const parse = (url) => { | ||
@@ -15,2 +14,17 @@ const parsed = new URL(url); | ||
}; | ||
export const delegateUrl = (url) => { | ||
const parsed = new URL(url); | ||
const source = parsed.searchParams.get("url"); | ||
if (!source || !source.startsWith("http")) { | ||
return false; | ||
} | ||
const cdn = getImageCdnForUrlByDomain(source); | ||
if (!cdn) { | ||
return false; | ||
} | ||
return { | ||
cdn, | ||
url: source, | ||
}; | ||
}; | ||
export const generate = ({ base, width, params: { quality = 75, root = "_vercel" } = {} }) => { | ||
@@ -25,3 +39,3 @@ // If the base is a relative URL, we need to add a dummy host to the URL | ||
}; | ||
export const transform = ({ url, width, height, cdn }) => { | ||
export const transform = ({ url, width, cdn }) => { | ||
// the URL might be relative, so we need to add a dummy host to it | ||
@@ -35,11 +49,3 @@ const parsedUrl = new URL(url, "http://n"); | ||
} | ||
if (src.startsWith("http")) { | ||
const cdn = getImageCdnForUrl(src); | ||
if (cdn && cdn !== "nextjs" && cdn !== "vercel") { | ||
return getTransformerForCdn(cdn)?.({ url: src, width, height }); | ||
} | ||
} | ||
else { | ||
setParamIfDefined(parsedUrl, "w", width, true, true); | ||
} | ||
setParamIfDefined(parsedUrl, "w", width, true, true); | ||
if (isNextImage) { | ||
@@ -46,0 +52,0 @@ if (parsedUrl.hostname === "n") { |
@@ -6,5 +6,6 @@ { | ||
"name": "unpic", | ||
"version": "3.2.0", | ||
"version": "3.3.0", | ||
"description": "Universal image CDN translator", | ||
"license": "MIT", | ||
"homepage": "https://unpic.pics/lib", | ||
"repository": { | ||
@@ -11,0 +12,0 @@ "type": "git", |
@@ -113,12 +113,15 @@ # 🖼 Unpic | ||
## Usage with Vercel / Next.js | ||
## Delegated URLs | ||
Unpic has special handling for Vercel and Next.js image URLs. It detects supported | ||
image CDNs, and falls back to `/_vercel/image` or `/_next/image` for local and | ||
unsupported remote images. | ||
Some transformers support URL delegation. This means that the source image URL | ||
is also checked, and if it matches a CDN then the transform is applied directly | ||
to the source image. For example: consider a `next/image` URL that points to an | ||
image on Shopify. The URL is detected as a `nextjs` URL because it starts with | ||
`/_next/image`. The `nextjs` transformer supports delegation, so the source | ||
image URL is then checked. As it matches a Shopify domain, the transform is | ||
applied directly to the Shopify URL. This means that the image is transformed on | ||
the fly by Shopify, rather than by Next.js. However if the source image is not a | ||
supported CDN, or is a local image then the `nextjs` transformer will return a | ||
`/_next/image` URL. | ||
For more information, see the | ||
[Unpic Vercel / Next.js](https://github.com/ascorbic/unpic/blob/main/vercel.md) | ||
documentation. | ||
## FAQs | ||
@@ -143,4 +146,4 @@ | ||
most CDN SDKs will not parse these URLs correctly. | ||
- **Can you add support for CDN X?** If it supports a URL API and has a public | ||
domain by which it can be identified then yes, please open an issue or PR. | ||
- **Can you add support for CDN X?** If it supports a URL API then yes, please | ||
open an issue or PR. | ||
- **Can you add my domain to CDN X?** If you provide a service where end-users | ||
@@ -170,18 +173,2 @@ use your URLs then probably. Examples may be image providers such as Unsplash, | ||
To add new domains or subdomains to an existing CDN, add them to `domains.json` | ||
or `subdomains.json` respectively. | ||
To add a new CDN, add the following: | ||
- a new source file in `src/transformers`. This should export a `transform` | ||
function that implements the `UrlTransformer` interface, a `parse` function | ||
that implements the `UrlParser` interface and optionally a `generate` function | ||
that implements the `UrlGenerator` interface. | ||
- a new test file in `src/transformers`. This should test all of the exported | ||
API functions. | ||
- at least one entry in `domains.json`, `subdomains.json` or `paths.json` to | ||
detect the CDN | ||
- add the new CDN to the types in `src/types.ts`, and import the new source file | ||
in `src/transform.ts` | ||
- add a sample image to `examples.json` in the demo site | ||
- ensure tests pass by installing Deno and running `deno test src` | ||
See the [contributing guide](CONTRIBUTING.md). |
@@ -21,1 +21,2 @@ "use strict"; | ||
__exportStar(require("./src/parse.js"), exports); | ||
__exportStar(require("./src/canonical.js"), exports); |
@@ -6,3 +6,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getImageCdnForUrl = void 0; | ||
exports.getImageCdnForUrlByPath = exports.getImageCdnForUrlByDomain = exports.getImageCdnForUrl = void 0; | ||
const domains_js_1 = __importDefault(require("../data/domains.js")); | ||
@@ -14,3 +14,7 @@ const subdomains_js_1 = __importDefault(require("../data/subdomains.js")); | ||
function getImageCdnForUrl(url) { | ||
const { hostname, pathname } = new URL(url); | ||
return getImageCdnForUrlByDomain(url) || getImageCdnForUrlByPath(url); | ||
} | ||
exports.getImageCdnForUrl = getImageCdnForUrl; | ||
function getImageCdnForUrlByDomain(url) { | ||
const { hostname } = new URL(url); | ||
if (cdnDomains.has(hostname)) { | ||
@@ -24,2 +28,7 @@ return cdnDomains.get(hostname); | ||
} | ||
return false; | ||
} | ||
exports.getImageCdnForUrlByDomain = getImageCdnForUrlByDomain; | ||
function getImageCdnForUrlByPath(url) { | ||
const { pathname } = new URL(url); | ||
for (const [prefix, cdn] of Object.entries(paths_js_1.default)) { | ||
@@ -32,2 +41,2 @@ if (pathname.startsWith(prefix)) { | ||
} | ||
exports.getImageCdnForUrl = getImageCdnForUrl; | ||
exports.getImageCdnForUrlByPath = getImageCdnForUrlByPath; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.transformUrl = exports.getTransformerForCdn = exports.getTransformerForUrl = exports.getTransformer = void 0; | ||
exports.getTransformerForUrl = exports.transformUrl = exports.getTransformerForCdn = exports.getTransformer = void 0; | ||
const detect_js_1 = require("./detect.js"); | ||
@@ -19,2 +19,3 @@ const contentful_js_1 = require("./transformers/contentful.js"); | ||
const keycdn_js_1 = require("./transformers/keycdn.js"); | ||
const canonical_js_1 = require("./canonical.js"); | ||
const getTransformer = (cdn) => ({ | ||
@@ -38,7 +39,2 @@ imgix: imgix_js_1.transform, | ||
/** | ||
* Returns a transformer function if the given URL is from a known image CDN | ||
*/ | ||
const getTransformerForUrl = (url) => (0, exports.getTransformerForCdn)((0, detect_js_1.getImageCdnForUrl)(url)); | ||
exports.getTransformerForUrl = getTransformerForUrl; | ||
/** | ||
* Returns a transformer function if the given CDN is supported | ||
@@ -58,7 +54,23 @@ */ | ||
const transformUrl = (options) => { | ||
if (options.cdn) { | ||
return (0, exports.getTransformerForCdn)(options.cdn)?.(options); | ||
const cdn = options?.cdn ?? (0, detect_js_1.getImageCdnForUrl)(options.url); | ||
// Default to recursive | ||
if (!(options.recursive ?? true)) { | ||
return (0, exports.getTransformerForCdn)(cdn)?.(options); | ||
} | ||
return (0, exports.getTransformerForUrl)(options.url)?.(options); | ||
const canonical = (0, canonical_js_1.getCanonicalCdnForUrl)(options.url, cdn); | ||
if (!canonical || !canonical.cdn) { | ||
return undefined; | ||
} | ||
return (0, exports.getTransformer)(canonical.cdn)?.({ | ||
...options, | ||
url: canonical.url, | ||
}); | ||
}; | ||
exports.transformUrl = transformUrl; | ||
/** | ||
* Returns a transformer function if the given URL is from a known image CDN | ||
* | ||
* @deprecated Use `getCanonicalCdnForUrl` and `getTransformerForCdn` instead | ||
*/ | ||
const getTransformerForUrl = (url) => (0, exports.getTransformerForCdn)((0, detect_js_1.getImageCdnForUrl)(url)); | ||
exports.getTransformerForUrl = getTransformerForUrl; |
@@ -27,4 +27,5 @@ "use strict"; | ||
(0, utils_js_1.setParamIfDefined)(url, "quality", (0, utils_js_1.getNumericParam)(url, "quality"), true); | ||
(0, utils_js_1.setParamIfUndefined)(url, "enlarge", 0); | ||
return url; | ||
}; | ||
exports.transform = transform; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.transform = exports.parse = void 0; | ||
const vercel_js_1 = require("./vercel.js"); | ||
const parse = (url) => ({ ...(0, vercel_js_1.parse)(url), cdn: "nextjs" }); | ||
exports.transform = exports.parse = exports.delegateUrl = void 0; | ||
var vercel_js_1 = require("./vercel.js"); | ||
Object.defineProperty(exports, "delegateUrl", { enumerable: true, get: function () { return vercel_js_1.delegateUrl; } }); | ||
const vercel_js_2 = require("./vercel.js"); | ||
const parse = (url) => ({ ...(0, vercel_js_2.parse)(url), cdn: "nextjs" }); | ||
exports.parse = parse; | ||
const transform = (params) => (0, vercel_js_1.transform)({ ...params, cdn: "nextjs" }); | ||
const transform = (params) => (0, vercel_js_2.transform)({ ...params, cdn: "nextjs" }); | ||
exports.transform = transform; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.transform = exports.generate = exports.parse = void 0; | ||
exports.transform = exports.generate = exports.delegateUrl = exports.parse = void 0; | ||
const utils_js_1 = require("../utils.js"); | ||
const transform_js_1 = require("../transform.js"); | ||
const detect_js_1 = require("../detect.js"); | ||
@@ -19,2 +18,18 @@ const parse = (url) => { | ||
exports.parse = parse; | ||
const delegateUrl = (url) => { | ||
const parsed = new URL(url); | ||
const source = parsed.searchParams.get("url"); | ||
if (!source || !source.startsWith("http")) { | ||
return false; | ||
} | ||
const cdn = (0, detect_js_1.getImageCdnForUrlByDomain)(source); | ||
if (!cdn) { | ||
return false; | ||
} | ||
return { | ||
cdn, | ||
url: source, | ||
}; | ||
}; | ||
exports.delegateUrl = delegateUrl; | ||
const generate = ({ base, width, params: { quality = 75, root = "_vercel" } = {} }) => { | ||
@@ -30,3 +45,3 @@ // If the base is a relative URL, we need to add a dummy host to the URL | ||
exports.generate = generate; | ||
const transform = ({ url, width, height, cdn }) => { | ||
const transform = ({ url, width, cdn }) => { | ||
// the URL might be relative, so we need to add a dummy host to it | ||
@@ -40,11 +55,3 @@ const parsedUrl = new URL(url, "http://n"); | ||
} | ||
if (src.startsWith("http")) { | ||
const cdn = (0, detect_js_1.getImageCdnForUrl)(src); | ||
if (cdn && cdn !== "nextjs" && cdn !== "vercel") { | ||
return (0, transform_js_1.getTransformerForCdn)(cdn)?.({ url: src, width, height }); | ||
} | ||
} | ||
else { | ||
(0, utils_js_1.setParamIfDefined)(parsedUrl, "w", width, true, true); | ||
} | ||
(0, utils_js_1.setParamIfDefined)(parsedUrl, "w", width, true, true); | ||
if (isNextImage) { | ||
@@ -51,0 +58,0 @@ if (parsedUrl.hostname === "n") { |
@@ -5,1 +5,2 @@ export * from "./src/types.js"; | ||
export * from "./src/parse.js"; | ||
export * from "./src/canonical.js"; |
import { ImageCdn } from "./types.js"; | ||
export declare function getImageCdnForUrl(url: string | URL): ImageCdn | false; | ||
export declare function getImageCdnForUrlByDomain(url: string | URL): ImageCdn | false; | ||
export declare function getImageCdnForUrlByPath(url: string | URL): ImageCdn | false; |
import { ImageCdn, UrlTransformer } from "./types.js"; | ||
export declare const getTransformer: (cdn: ImageCdn) => UrlTransformer; | ||
/** | ||
* Returns a transformer function if the given URL is from a known image CDN | ||
*/ | ||
export declare const getTransformerForUrl: (url: string | URL) => UrlTransformer | undefined; | ||
/** | ||
* Returns a transformer function if the given CDN is supported | ||
@@ -16,1 +12,7 @@ */ | ||
export declare const transformUrl: UrlTransformer; | ||
/** | ||
* Returns a transformer function if the given URL is from a known image CDN | ||
* | ||
* @deprecated Use `getCanonicalCdnForUrl` and `getTransformerForCdn` instead | ||
*/ | ||
export declare const getTransformerForUrl: (url: string | URL) => UrlTransformer | undefined; |
import { UrlParser, UrlTransformer } from "../types.js"; | ||
export { delegateUrl } from "./vercel.js"; | ||
export declare const parse: UrlParser; | ||
export declare const transform: UrlTransformer; |
@@ -1,3 +0,4 @@ | ||
import { UrlGenerator, UrlParser, UrlTransformer } from "../types.js"; | ||
import { ShouldDelegateUrl, UrlGenerator, UrlParser, UrlTransformer } from "../types.js"; | ||
export declare const parse: UrlParser; | ||
export declare const delegateUrl: ShouldDelegateUrl; | ||
export interface VercelParams { | ||
@@ -4,0 +5,0 @@ quality?: number; |
@@ -13,5 +13,26 @@ /** | ||
format?: string; | ||
/** Recursively find the the canonical CDN for a source image. Default is true */ | ||
recursive?: boolean; | ||
/** Specify a CDN rather than auto-detecting */ | ||
cdn?: ImageCdn; | ||
/** CDN-specific options. */ | ||
cdnOptions?: CdnOptions; | ||
} | ||
export interface CanonicalCdnUrl { | ||
/** The source image URL */ | ||
url: string | URL; | ||
/** The CDN to use */ | ||
cdn: ImageCdn; | ||
} | ||
/** | ||
* Asks a CDN if there is a different canonical CDN for the given URL | ||
* @param url The URL to check | ||
* @returns The canonical CDN URL, or false if the given CDN will handle it itself | ||
*/ | ||
export interface ShouldDelegateUrl { | ||
(url: string | URL): CanonicalCdnUrl | false; | ||
} | ||
export declare type CdnOptions = { | ||
[key in ImageCdn]: Record<string, unknown>; | ||
}; | ||
export interface UrlGeneratorOptions<TParams = Record<string, string>> { | ||
@@ -18,0 +39,0 @@ base: string | URL; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
81994
77
2038
0
172