cloudflare-next-intl 0.10.30 → 0.10.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/src/image_optimizer/detect_loader.d.ts +20 -0
- package/dist/src/image_optimizer/detect_loader.js +150 -0
- package/dist/src/image_optimizer/index.d.ts +2 -1
- package/dist/src/image_optimizer/index.js +2 -1
- package/dist/src/image_optimizer/plugin.js +12 -0
- package/dist/src/image_optimizer/types.d.ts +3 -0
- package/dist/src/image_optimizer/types.js +2 -0
- package/dist/src/vite/auto_image_loader_plugin.d.ts +2 -20
- package/dist/src/vite/auto_image_loader_plugin.js +2 -150
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -312,6 +312,7 @@ export default defineConfig({
|
|
|
312
312
|
overrides: { // Per-image overrides keyed by public src path (wins over scanned <Image> props)
|
|
313
313
|
"/images/hero.png": { maxWidth: false, formats: ["webp"], blur: { quality: 80 } },
|
|
314
314
|
},
|
|
315
|
+
autoImageLoader: true, // Auto-detect custom image loader for standalone imageOptimizer usages (default: true, or false/options)
|
|
315
316
|
},
|
|
316
317
|
autoImageLoader: true, // Auto-detect custom image loader (from next.config.* loaderFile or image-loader.ts; default: true, or options object)
|
|
317
318
|
autoClientMessages: true, // Auto-scan client useTranslations calls to trim client payload (default: true, or options object)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const VIRTUAL_IMAGE_LOADER_ID = "virtual:cloudflare-next-intl-image-loader";
|
|
2
|
+
export declare const RESOLVED_IMAGE_LOADER_ID: string;
|
|
3
|
+
export declare const NEXT_CONFIG_CANDIDATES: readonly ["next.config.ts", "next.config.mts", "next.config.mjs", "next.config.js", "next.config.cjs"];
|
|
4
|
+
export declare const DEFAULT_LOADER_CANDIDATES: readonly ["image-loader.ts", "image-loader.js", "image-loader.mjs", "image-loader.cjs", "image-loader.tsx", "image-loader.jsx", "src/image-loader.ts", "src/image-loader.js", "src/image-loader.mjs", "src/image-loader.cjs", "src/image-loader.tsx", "src/image-loader.jsx"];
|
|
5
|
+
export interface AutoImageLoaderOptions {
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
file?: string;
|
|
8
|
+
exportName?: string;
|
|
9
|
+
root?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function findLoaderFileFromNextConfig(root: string): string | null;
|
|
12
|
+
export interface DetectedImageLoader {
|
|
13
|
+
exists: boolean;
|
|
14
|
+
isEmpty: boolean;
|
|
15
|
+
loaderPath: string | null;
|
|
16
|
+
exportName: string | null;
|
|
17
|
+
}
|
|
18
|
+
export declare function findExportedLoaderName(code: string, preferredExportName?: string): string | null;
|
|
19
|
+
export declare function detectImageLoader(options?: AutoImageLoaderOptions): DetectedImageLoader;
|
|
20
|
+
export declare function generateLoaderVirtualModule(detected: DetectedImageLoader): string;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export const VIRTUAL_IMAGE_LOADER_ID = "virtual:cloudflare-next-intl-image-loader";
|
|
4
|
+
export const RESOLVED_IMAGE_LOADER_ID = "\0" + VIRTUAL_IMAGE_LOADER_ID;
|
|
5
|
+
export const NEXT_CONFIG_CANDIDATES = [
|
|
6
|
+
"next.config.ts",
|
|
7
|
+
"next.config.mts",
|
|
8
|
+
"next.config.mjs",
|
|
9
|
+
"next.config.js",
|
|
10
|
+
"next.config.cjs",
|
|
11
|
+
];
|
|
12
|
+
export const DEFAULT_LOADER_CANDIDATES = [
|
|
13
|
+
"image-loader.ts",
|
|
14
|
+
"image-loader.js",
|
|
15
|
+
"image-loader.mjs",
|
|
16
|
+
"image-loader.cjs",
|
|
17
|
+
"image-loader.tsx",
|
|
18
|
+
"image-loader.jsx",
|
|
19
|
+
"src/image-loader.ts",
|
|
20
|
+
"src/image-loader.js",
|
|
21
|
+
"src/image-loader.mjs",
|
|
22
|
+
"src/image-loader.cjs",
|
|
23
|
+
"src/image-loader.tsx",
|
|
24
|
+
"src/image-loader.jsx",
|
|
25
|
+
];
|
|
26
|
+
export function findLoaderFileFromNextConfig(root) {
|
|
27
|
+
for (const name of NEXT_CONFIG_CANDIDATES) {
|
|
28
|
+
const configPath = path.resolve(root, name);
|
|
29
|
+
if (!existsSync(configPath))
|
|
30
|
+
continue;
|
|
31
|
+
const raw = readFileSync(configPath, "utf8");
|
|
32
|
+
const stripped = raw
|
|
33
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
34
|
+
.replace(/\/\/.*/g, "");
|
|
35
|
+
const match = stripped.match(/loaderFile\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
36
|
+
if (match?.[1]) {
|
|
37
|
+
const candidate = match[1].trim();
|
|
38
|
+
const resolved = path.isAbsolute(candidate) ? candidate : path.resolve(root, candidate);
|
|
39
|
+
if (existsSync(resolved)) {
|
|
40
|
+
return resolved;
|
|
41
|
+
}
|
|
42
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
43
|
+
for (const ext of extensions) {
|
|
44
|
+
if (existsSync(resolved + ext)) {
|
|
45
|
+
return resolved + ext;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
export function findExportedLoaderName(code, preferredExportName) {
|
|
53
|
+
const stripped = code
|
|
54
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
55
|
+
.replace(/\/\/.*/g, "");
|
|
56
|
+
if (preferredExportName) {
|
|
57
|
+
if (preferredExportName === "default") {
|
|
58
|
+
if (/export\s+default\b/.test(stripped) || /export\s*\{[^}]*\bas\s+default\b[^}]*\}/.test(stripped)) {
|
|
59
|
+
return "default";
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const fnRegex = new RegExp(`export\\s+(?:async\\s+)?function\\s+${preferredExportName}\\b`);
|
|
64
|
+
if (fnRegex.test(stripped))
|
|
65
|
+
return preferredExportName;
|
|
66
|
+
const constRegex = new RegExp(`export\\s+(?:const|let|var)\\s+${preferredExportName}\\b`);
|
|
67
|
+
if (constRegex.test(stripped))
|
|
68
|
+
return preferredExportName;
|
|
69
|
+
const reExportRegex = new RegExp(`export\\s*\\{[^}]*\\b(?:as\\s+)?${preferredExportName}\\b[^}]*\\}`);
|
|
70
|
+
if (reExportRegex.test(stripped))
|
|
71
|
+
return preferredExportName;
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (/export\s+default\b/.test(stripped) || /export\s*\{[^}]*\bas\s+default\b[^}]*\}/.test(stripped)) {
|
|
75
|
+
return "default";
|
|
76
|
+
}
|
|
77
|
+
const fnMatch = stripped.match(/export\s+(?:async\s+)?function\s+([a-zA-Z0-9_$]+)/);
|
|
78
|
+
if (fnMatch?.[1]) {
|
|
79
|
+
return fnMatch[1];
|
|
80
|
+
}
|
|
81
|
+
const varMatch = stripped.match(/export\s+(?:const|let|var)\s+([a-zA-Z0-9_$]+)/);
|
|
82
|
+
if (varMatch?.[1]) {
|
|
83
|
+
return varMatch[1];
|
|
84
|
+
}
|
|
85
|
+
const exportBlockMatch = stripped.match(/export\s*\{([^}]+)\}/);
|
|
86
|
+
if (exportBlockMatch?.[1]) {
|
|
87
|
+
const entries = exportBlockMatch[1].split(",");
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
const parts = entry.trim().split(/\s+as\s+/);
|
|
90
|
+
const exportedName = parts[parts.length - 1]?.trim();
|
|
91
|
+
if (exportedName) {
|
|
92
|
+
return exportedName;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
export function detectImageLoader(options) {
|
|
99
|
+
const root = options?.root ? path.resolve(options.root) : process.cwd();
|
|
100
|
+
let targetPath = null;
|
|
101
|
+
if (options?.file) {
|
|
102
|
+
const customPath = path.isAbsolute(options.file) ? options.file : path.resolve(root, options.file);
|
|
103
|
+
if (existsSync(customPath)) {
|
|
104
|
+
targetPath = customPath;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
return { exists: false, isEmpty: false, loaderPath: null, exportName: null };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const configLoader = findLoaderFileFromNextConfig(root);
|
|
112
|
+
if (configLoader) {
|
|
113
|
+
targetPath = configLoader;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
for (const candidate of DEFAULT_LOADER_CANDIDATES) {
|
|
117
|
+
const candidatePath = path.resolve(root, candidate);
|
|
118
|
+
if (existsSync(candidatePath)) {
|
|
119
|
+
targetPath = candidatePath;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!targetPath) {
|
|
126
|
+
return { exists: false, isEmpty: false, loaderPath: null, exportName: null };
|
|
127
|
+
}
|
|
128
|
+
const content = readFileSync(targetPath, "utf8");
|
|
129
|
+
const stripped = content.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*/g, "").trim();
|
|
130
|
+
if (!stripped) {
|
|
131
|
+
return { exists: true, isEmpty: true, loaderPath: targetPath, exportName: null };
|
|
132
|
+
}
|
|
133
|
+
const exportName = findExportedLoaderName(content, options?.exportName);
|
|
134
|
+
return {
|
|
135
|
+
exists: true,
|
|
136
|
+
isEmpty: false,
|
|
137
|
+
loaderPath: targetPath,
|
|
138
|
+
exportName,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export function generateLoaderVirtualModule(detected) {
|
|
142
|
+
if (!detected.exists || detected.isEmpty || !detected.loaderPath || !detected.exportName) {
|
|
143
|
+
return `export const defaultLoader = undefined;\nexport const hasCustomLoader = false;\nexport default undefined;\n`;
|
|
144
|
+
}
|
|
145
|
+
const normalizedPath = detected.loaderPath.replace(/\\/g, "/");
|
|
146
|
+
if (detected.exportName === "default") {
|
|
147
|
+
return `import loader from ${JSON.stringify(normalizedPath)};\nexport const defaultLoader = loader;\nexport const hasCustomLoader = true;\nexport default loader;\n`;
|
|
148
|
+
}
|
|
149
|
+
return `import { ${detected.exportName} as loader } from ${JSON.stringify(normalizedPath)};\nexport const defaultLoader = loader;\nexport const hasCustomLoader = true;\nexport default loader;\n`;
|
|
150
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { imageOptimizerPlugin, imageOptimizer, VIRTUAL_IMAGE_SHIM_ID, default, } from "./plugin.js";
|
|
1
|
+
export { imageOptimizerPlugin, imageOptimizer, VIRTUAL_IMAGE_SHIM_ID, VIRTUAL_MANIFEST_ID, getShimPath, default, } from "./plugin.js";
|
|
2
2
|
export { resolveOptions, resolveImageConfig, resolveBlurOptions, DEFAULT_OPTIONS, DEFAULT_BLUR_OPTIONS, SUPPORTED_EXTENSIONS, type ImageFormat, type ImageBlurOptions, type ImageOverrideOptions, type ImageOptimizerPluginOptions, type ResolvedBlurOptions, type ResolvedOptions, type ResolvedImageConfig, type OptimizedImage, type ManifestData, } from "./types.js";
|
|
3
3
|
export { processImage, makeBlurDataURL, toGeneratedPath, toPublicSrc, } from "./process_image.js";
|
|
4
4
|
export { renderManifest, writeManifest, } from "./manifest.js";
|
|
@@ -7,3 +7,4 @@ export { collectImages, run, } from "./run.js";
|
|
|
7
7
|
export { collectUsedImages, findCodeFiles, extractImageReferences, CODE_EXTENSIONS, IGNORED_DIRS, } from "./scan_used.js";
|
|
8
8
|
export { getImageBlurSvg, } from "./blur_svg.js";
|
|
9
9
|
export { type ManifestEntry, } from "./next_image_shim.js";
|
|
10
|
+
export { VIRTUAL_IMAGE_LOADER_ID, RESOLVED_IMAGE_LOADER_ID, type AutoImageLoaderOptions, type DetectedImageLoader, findLoaderFileFromNextConfig, findExportedLoaderName, detectImageLoader, generateLoaderVirtualModule, } from "./detect_loader.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { imageOptimizerPlugin, imageOptimizer, VIRTUAL_IMAGE_SHIM_ID, default, } from "./plugin.js";
|
|
1
|
+
export { imageOptimizerPlugin, imageOptimizer, VIRTUAL_IMAGE_SHIM_ID, VIRTUAL_MANIFEST_ID, getShimPath, default, } from "./plugin.js";
|
|
2
2
|
export { resolveOptions, resolveImageConfig, resolveBlurOptions, DEFAULT_OPTIONS, DEFAULT_BLUR_OPTIONS, SUPPORTED_EXTENSIONS, } from "./types.js";
|
|
3
3
|
export { processImage, makeBlurDataURL, toGeneratedPath, toPublicSrc, } from "./process_image.js";
|
|
4
4
|
export { renderManifest, writeManifest, } from "./manifest.js";
|
|
@@ -6,3 +6,4 @@ export { isFresh, loadCache, saveCache, } from "./cache.js";
|
|
|
6
6
|
export { collectImages, run, } from "./run.js";
|
|
7
7
|
export { collectUsedImages, findCodeFiles, extractImageReferences, CODE_EXTENSIONS, IGNORED_DIRS, } from "./scan_used.js";
|
|
8
8
|
export { getImageBlurSvg, } from "./blur_svg.js";
|
|
9
|
+
export { VIRTUAL_IMAGE_LOADER_ID, RESOLVED_IMAGE_LOADER_ID, findLoaderFileFromNextConfig, findExportedLoaderName, detectImageLoader, generateLoaderVirtualModule, } from "./detect_loader.js";
|
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { run } from "./run.js";
|
|
5
5
|
import { resolveOptions } from "./types.js";
|
|
6
|
+
import { VIRTUAL_IMAGE_LOADER_ID, RESOLVED_IMAGE_LOADER_ID, detectImageLoader, generateLoaderVirtualModule, } from "./detect_loader.js";
|
|
6
7
|
export const VIRTUAL_IMAGE_SHIM_ID = "virtual:cloudflare-next-intl-image";
|
|
7
8
|
export const VIRTUAL_MANIFEST_ID = "virtual:cloudflare-next-intl-images-manifest";
|
|
8
9
|
const RESOLVED_MANIFEST_ID = "\0" + VIRTUAL_MANIFEST_ID;
|
|
@@ -28,6 +29,9 @@ export function imageOptimizerPlugin(options) {
|
|
|
28
29
|
if (id === VIRTUAL_MANIFEST_ID) {
|
|
29
30
|
return RESOLVED_MANIFEST_ID;
|
|
30
31
|
}
|
|
32
|
+
if (id === VIRTUAL_IMAGE_LOADER_ID) {
|
|
33
|
+
return RESOLVED_IMAGE_LOADER_ID;
|
|
34
|
+
}
|
|
31
35
|
return undefined;
|
|
32
36
|
},
|
|
33
37
|
load(id) {
|
|
@@ -39,6 +43,14 @@ export function imageOptimizerPlugin(options) {
|
|
|
39
43
|
}
|
|
40
44
|
return `export default { images: {} };`;
|
|
41
45
|
}
|
|
46
|
+
if (id === RESOLVED_IMAGE_LOADER_ID) {
|
|
47
|
+
if (resolved.autoImageLoader === false) {
|
|
48
|
+
return `export const defaultLoader = undefined;\nexport const hasCustomLoader = false;\nexport default undefined;\n`;
|
|
49
|
+
}
|
|
50
|
+
const loaderOptions = typeof resolved.autoImageLoader === "object" ? resolved.autoImageLoader : undefined;
|
|
51
|
+
const detected = detectImageLoader(loaderOptions);
|
|
52
|
+
return generateLoaderVirtualModule(detected);
|
|
53
|
+
}
|
|
42
54
|
return undefined;
|
|
43
55
|
},
|
|
44
56
|
transform(code, id) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AutoImageLoaderOptions } from "./detect_loader.js";
|
|
1
2
|
export type ImageFormat = "avif" | "webp" | "png" | "jpeg" | "gif" | "tiff" | "heif" | "jp2" | "jxl";
|
|
2
3
|
export interface ImageBlurOptions {
|
|
3
4
|
enabled?: boolean;
|
|
@@ -28,6 +29,7 @@ export interface ImageOptimizerPluginOptions {
|
|
|
28
29
|
overrides?: Record<string, ImageOverrideOptions>;
|
|
29
30
|
concurrency?: number;
|
|
30
31
|
effort?: number;
|
|
32
|
+
autoImageLoader?: boolean | AutoImageLoaderOptions;
|
|
31
33
|
}
|
|
32
34
|
export interface ResolvedBlurOptions {
|
|
33
35
|
enabled: boolean;
|
|
@@ -50,6 +52,7 @@ export interface ResolvedOptions {
|
|
|
50
52
|
overrides: Record<string, ImageOverrideOptions>;
|
|
51
53
|
concurrency: number;
|
|
52
54
|
effort: number | undefined;
|
|
55
|
+
autoImageLoader: boolean | AutoImageLoaderOptions;
|
|
53
56
|
}
|
|
54
57
|
export interface ResolvedImageConfig {
|
|
55
58
|
maxWidth: number | false;
|
|
@@ -21,6 +21,7 @@ export const DEFAULT_OPTIONS = {
|
|
|
21
21
|
overrides: {},
|
|
22
22
|
concurrency: Math.max(1, Math.min(cpus().length, 8)),
|
|
23
23
|
effort: undefined,
|
|
24
|
+
autoImageLoader: true,
|
|
24
25
|
};
|
|
25
26
|
export function resolveBlurOptions(blur, parentDefault = DEFAULT_BLUR_OPTIONS) {
|
|
26
27
|
if (blur === false) {
|
|
@@ -59,6 +60,7 @@ export function resolveOptions(options) {
|
|
|
59
60
|
overrides: raw.overrides ? { ...raw.overrides } : {},
|
|
60
61
|
concurrency: Math.max(1, raw.concurrency ?? DEFAULT_OPTIONS.concurrency),
|
|
61
62
|
effort: raw.effort,
|
|
63
|
+
autoImageLoader: raw.autoImageLoader ?? DEFAULT_OPTIONS.autoImageLoader,
|
|
62
64
|
};
|
|
63
65
|
}
|
|
64
66
|
export function resolveImageConfig(publicSrc, options) {
|
|
@@ -1,23 +1,5 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
export declare const NEXT_CONFIG_CANDIDATES: readonly ["next.config.ts", "next.config.mts", "next.config.mjs", "next.config.js", "next.config.cjs"];
|
|
5
|
-
export declare const DEFAULT_LOADER_CANDIDATES: readonly ["image-loader.ts", "image-loader.js", "image-loader.mjs", "image-loader.cjs", "image-loader.tsx", "image-loader.jsx", "src/image-loader.ts", "src/image-loader.js", "src/image-loader.mjs", "src/image-loader.cjs", "src/image-loader.tsx", "src/image-loader.jsx"];
|
|
6
|
-
export interface AutoImageLoaderOptions {
|
|
7
|
-
enabled?: boolean;
|
|
8
|
-
file?: string;
|
|
9
|
-
exportName?: string;
|
|
10
|
-
root?: string;
|
|
11
|
-
}
|
|
12
|
-
export declare function findLoaderFileFromNextConfig(root: string): string | null;
|
|
13
|
-
export interface DetectedImageLoader {
|
|
14
|
-
exists: boolean;
|
|
15
|
-
isEmpty: boolean;
|
|
16
|
-
loaderPath: string | null;
|
|
17
|
-
exportName: string | null;
|
|
18
|
-
}
|
|
19
|
-
export declare function findExportedLoaderName(code: string, preferredExportName?: string): string | null;
|
|
20
|
-
export declare function detectImageLoader(options?: AutoImageLoaderOptions): DetectedImageLoader;
|
|
21
|
-
export declare function generateLoaderVirtualModule(detected: DetectedImageLoader): string;
|
|
2
|
+
import { NEXT_CONFIG_CANDIDATES, DEFAULT_LOADER_CANDIDATES, VIRTUAL_IMAGE_LOADER_ID, RESOLVED_IMAGE_LOADER_ID, type AutoImageLoaderOptions, type DetectedImageLoader, findLoaderFileFromNextConfig, findExportedLoaderName, detectImageLoader, generateLoaderVirtualModule } from "../image_optimizer/detect_loader.js";
|
|
3
|
+
export { NEXT_CONFIG_CANDIDATES, DEFAULT_LOADER_CANDIDATES, VIRTUAL_IMAGE_LOADER_ID, RESOLVED_IMAGE_LOADER_ID, type AutoImageLoaderOptions, type DetectedImageLoader, findLoaderFileFromNextConfig, findExportedLoaderName, detectImageLoader, generateLoaderVirtualModule, };
|
|
22
4
|
export declare function autoImageLoaderPlugin(options?: AutoImageLoaderOptions): Plugin;
|
|
23
5
|
export default autoImageLoaderPlugin;
|
|
@@ -1,154 +1,6 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
1
|
import { getShimPath, VIRTUAL_IMAGE_SHIM_ID } from "../image_optimizer/plugin.js";
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
export const NEXT_CONFIG_CANDIDATES = [
|
|
7
|
-
"next.config.ts",
|
|
8
|
-
"next.config.mts",
|
|
9
|
-
"next.config.mjs",
|
|
10
|
-
"next.config.js",
|
|
11
|
-
"next.config.cjs",
|
|
12
|
-
];
|
|
13
|
-
export const DEFAULT_LOADER_CANDIDATES = [
|
|
14
|
-
"image-loader.ts",
|
|
15
|
-
"image-loader.js",
|
|
16
|
-
"image-loader.mjs",
|
|
17
|
-
"image-loader.cjs",
|
|
18
|
-
"image-loader.tsx",
|
|
19
|
-
"image-loader.jsx",
|
|
20
|
-
"src/image-loader.ts",
|
|
21
|
-
"src/image-loader.js",
|
|
22
|
-
"src/image-loader.mjs",
|
|
23
|
-
"src/image-loader.cjs",
|
|
24
|
-
"src/image-loader.tsx",
|
|
25
|
-
"src/image-loader.jsx",
|
|
26
|
-
];
|
|
27
|
-
export function findLoaderFileFromNextConfig(root) {
|
|
28
|
-
for (const name of NEXT_CONFIG_CANDIDATES) {
|
|
29
|
-
const configPath = path.resolve(root, name);
|
|
30
|
-
if (!existsSync(configPath))
|
|
31
|
-
continue;
|
|
32
|
-
const raw = readFileSync(configPath, "utf8");
|
|
33
|
-
const stripped = raw
|
|
34
|
-
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
35
|
-
.replace(/\/\/.*/g, "");
|
|
36
|
-
const match = stripped.match(/loaderFile\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
37
|
-
if (match?.[1]) {
|
|
38
|
-
const candidate = match[1].trim();
|
|
39
|
-
const resolved = path.isAbsolute(candidate) ? candidate : path.resolve(root, candidate);
|
|
40
|
-
if (existsSync(resolved)) {
|
|
41
|
-
return resolved;
|
|
42
|
-
}
|
|
43
|
-
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
44
|
-
for (const ext of extensions) {
|
|
45
|
-
if (existsSync(resolved + ext)) {
|
|
46
|
-
return resolved + ext;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
export function findExportedLoaderName(code, preferredExportName) {
|
|
54
|
-
const stripped = code
|
|
55
|
-
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
56
|
-
.replace(/\/\/.*/g, "");
|
|
57
|
-
if (preferredExportName) {
|
|
58
|
-
if (preferredExportName === "default") {
|
|
59
|
-
if (/export\s+default\b/.test(stripped) || /export\s*\{[^}]*\bas\s+default\b[^}]*\}/.test(stripped)) {
|
|
60
|
-
return "default";
|
|
61
|
-
}
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
const fnRegex = new RegExp(`export\\s+(?:async\\s+)?function\\s+${preferredExportName}\\b`);
|
|
65
|
-
if (fnRegex.test(stripped))
|
|
66
|
-
return preferredExportName;
|
|
67
|
-
const constRegex = new RegExp(`export\\s+(?:const|let|var)\\s+${preferredExportName}\\b`);
|
|
68
|
-
if (constRegex.test(stripped))
|
|
69
|
-
return preferredExportName;
|
|
70
|
-
const reExportRegex = new RegExp(`export\\s*\\{[^}]*\\b(?:as\\s+)?${preferredExportName}\\b[^}]*\\}`);
|
|
71
|
-
if (reExportRegex.test(stripped))
|
|
72
|
-
return preferredExportName;
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
if (/export\s+default\b/.test(stripped) || /export\s*\{[^}]*\bas\s+default\b[^}]*\}/.test(stripped)) {
|
|
76
|
-
return "default";
|
|
77
|
-
}
|
|
78
|
-
const fnMatch = stripped.match(/export\s+(?:async\s+)?function\s+([a-zA-Z0-9_$]+)/);
|
|
79
|
-
if (fnMatch?.[1]) {
|
|
80
|
-
return fnMatch[1];
|
|
81
|
-
}
|
|
82
|
-
const varMatch = stripped.match(/export\s+(?:const|let|var)\s+([a-zA-Z0-9_$]+)/);
|
|
83
|
-
if (varMatch?.[1]) {
|
|
84
|
-
return varMatch[1];
|
|
85
|
-
}
|
|
86
|
-
const exportBlockMatch = stripped.match(/export\s*\{([^}]+)\}/);
|
|
87
|
-
if (exportBlockMatch?.[1]) {
|
|
88
|
-
const entries = exportBlockMatch[1].split(",");
|
|
89
|
-
for (const entry of entries) {
|
|
90
|
-
const parts = entry.trim().split(/\s+as\s+/);
|
|
91
|
-
const exportedName = parts[parts.length - 1]?.trim();
|
|
92
|
-
if (exportedName) {
|
|
93
|
-
return exportedName;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
export function detectImageLoader(options) {
|
|
100
|
-
const root = options?.root ? path.resolve(options.root) : process.cwd();
|
|
101
|
-
let targetPath = null;
|
|
102
|
-
if (options?.file) {
|
|
103
|
-
const customPath = path.isAbsolute(options.file) ? options.file : path.resolve(root, options.file);
|
|
104
|
-
if (existsSync(customPath)) {
|
|
105
|
-
targetPath = customPath;
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
return { exists: false, isEmpty: false, loaderPath: null, exportName: null };
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
const configLoader = findLoaderFileFromNextConfig(root);
|
|
113
|
-
if (configLoader) {
|
|
114
|
-
targetPath = configLoader;
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
for (const candidate of DEFAULT_LOADER_CANDIDATES) {
|
|
118
|
-
const candidatePath = path.resolve(root, candidate);
|
|
119
|
-
if (existsSync(candidatePath)) {
|
|
120
|
-
targetPath = candidatePath;
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (!targetPath) {
|
|
127
|
-
return { exists: false, isEmpty: false, loaderPath: null, exportName: null };
|
|
128
|
-
}
|
|
129
|
-
const content = readFileSync(targetPath, "utf8");
|
|
130
|
-
const stripped = content.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*/g, "").trim();
|
|
131
|
-
if (!stripped) {
|
|
132
|
-
return { exists: true, isEmpty: true, loaderPath: targetPath, exportName: null };
|
|
133
|
-
}
|
|
134
|
-
const exportName = findExportedLoaderName(content, options?.exportName);
|
|
135
|
-
return {
|
|
136
|
-
exists: true,
|
|
137
|
-
isEmpty: false,
|
|
138
|
-
loaderPath: targetPath,
|
|
139
|
-
exportName,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
export function generateLoaderVirtualModule(detected) {
|
|
143
|
-
if (!detected.exists || detected.isEmpty || !detected.loaderPath || !detected.exportName) {
|
|
144
|
-
return `export const defaultLoader = undefined;\nexport const hasCustomLoader = false;\nexport default undefined;\n`;
|
|
145
|
-
}
|
|
146
|
-
const normalizedPath = detected.loaderPath.replace(/\\/g, "/");
|
|
147
|
-
if (detected.exportName === "default") {
|
|
148
|
-
return `import loader from ${JSON.stringify(normalizedPath)};\nexport const defaultLoader = loader;\nexport const hasCustomLoader = true;\nexport default loader;\n`;
|
|
149
|
-
}
|
|
150
|
-
return `import { ${detected.exportName} as loader } from ${JSON.stringify(normalizedPath)};\nexport const defaultLoader = loader;\nexport const hasCustomLoader = true;\nexport default loader;\n`;
|
|
151
|
-
}
|
|
2
|
+
import { NEXT_CONFIG_CANDIDATES, DEFAULT_LOADER_CANDIDATES, VIRTUAL_IMAGE_LOADER_ID, RESOLVED_IMAGE_LOADER_ID, findLoaderFileFromNextConfig, findExportedLoaderName, detectImageLoader, generateLoaderVirtualModule, } from "../image_optimizer/detect_loader.js";
|
|
3
|
+
export { NEXT_CONFIG_CANDIDATES, DEFAULT_LOADER_CANDIDATES, VIRTUAL_IMAGE_LOADER_ID, RESOLVED_IMAGE_LOADER_ID, findLoaderFileFromNextConfig, findExportedLoaderName, detectImageLoader, generateLoaderVirtualModule, };
|
|
152
4
|
export function autoImageLoaderPlugin(options) {
|
|
153
5
|
const isEnabled = options?.enabled !== false;
|
|
154
6
|
return {
|