[@astrojs/image] adds a logger to the the image integration (#4342)
* WIP: adding a console logger that respect vite.logLevel * adds an optional prefix for messages * remove temporary debug log * typo fix * cleaning up log syntax * fixing logger whitespace * adding README docs * test: disable integration logging in tests * chore: add changeset
This commit is contained in:
parent
4ac2718883
commit
c4af8723bd
10 changed files with 149 additions and 19 deletions
5
.changeset/itchy-crews-care.md
Normal file
5
.changeset/itchy-crews-care.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@astrojs/image': patch
|
||||
---
|
||||
|
||||
The integration now includes a logger to better track progress in SSG builds. Use the new `logLevel: "debug"` integration option to see detailed logs of every image transformation built in your project.
|
|
@ -7,6 +7,7 @@ This **[Astro integration][astro-integration]** makes it easy to optimize images
|
|||
- <strong>[Why `@astrojs/image`?](#why-astrojsimage)</strong>
|
||||
- <strong>[Installation](#installation)</strong>
|
||||
- <strong>[Usage](#usage)</strong>
|
||||
- <strong>[Debugging](#debugging)</strong>
|
||||
- <strong>[Configuration](#configuration)</strong>
|
||||
- <strong>[Examples](#examples)</strong>
|
||||
- <strong>[Troubleshooting](#troubleshooting)</strong>
|
||||
|
@ -272,8 +273,6 @@ The integration can be configured to run with a different image service, either
|
|||
|
||||
> During development, local images may not have been published yet and would not be available to hosted image services. Local images will always use the built-in `sharp` service when using `astro dev`.
|
||||
|
||||
There are currently no other configuration options for the `@astrojs/image` integration. Please [open an issue](https://github.com/withastro/astro/issues/new/choose) if you have a compelling use case to share.
|
||||
|
||||
|
||||
### config.serviceEntryPoint
|
||||
|
||||
|
@ -291,6 +290,23 @@ export default {
|
|||
}
|
||||
```
|
||||
|
||||
### config.logLevel
|
||||
|
||||
The `logLevel` controls can be used to control how much detail is logged by the integration during builds. This may be useful to track down a specific image or transformation that is taking a long time to build.
|
||||
|
||||
```js
|
||||
// astro.config.mjs
|
||||
import image from '@astrojs/image';
|
||||
|
||||
export default {
|
||||
integrations: [image({
|
||||
// supported levels: 'debug' | 'info' | 'warn' | 'error' | 'silent'
|
||||
// default: 'info'
|
||||
logLevel: 'debug'
|
||||
})],
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Local images
|
||||
|
|
|
@ -52,6 +52,8 @@
|
|||
"@types/etag": "^1.8.1",
|
||||
"@types/sharp": "^0.30.4",
|
||||
"astro": "workspace:*",
|
||||
"astro-scripts": "workspace:*"
|
||||
"astro-scripts": "workspace:*",
|
||||
"kleur": "^4.1.4",
|
||||
"tiny-glob": "^0.2.9"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { bgGreen, black, cyan, dim, green, bold } from 'kleur/colors';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
@ -5,19 +6,30 @@ import { OUTPUT_DIR } from '../constants.js';
|
|||
import type { SSRImageService, TransformOptions } from '../loaders/index.js';
|
||||
import { isRemoteImage, loadLocalImage, loadRemoteImage } from '../utils/images.js';
|
||||
import { ensureDir } from '../utils/paths.js';
|
||||
import { debug, info, warn, LoggerLevel } from '../utils/logger.js';
|
||||
|
||||
function getTimeStat(timeStart: number, timeEnd: number) {
|
||||
const buildTime = timeEnd - timeStart;
|
||||
return buildTime < 750 ? `${Math.round(buildTime)}ms` : `${(buildTime / 1000).toFixed(2)}s`;
|
||||
}
|
||||
|
||||
export interface SSGBuildParams {
|
||||
loader: SSRImageService;
|
||||
staticImages: Map<string, Map<string, TransformOptions>>;
|
||||
srcDir: URL;
|
||||
outDir: URL;
|
||||
logLevel: LoggerLevel;
|
||||
}
|
||||
|
||||
export async function ssgBuild({ loader, staticImages, srcDir, outDir }: SSGBuildParams) {
|
||||
export async function ssgBuild({ loader, staticImages, srcDir, outDir, logLevel }: SSGBuildParams) {
|
||||
const timer = performance.now();
|
||||
|
||||
info({ level: logLevel, prefix: false, message: `${bgGreen(black(` optimizing ${staticImages.size} image${staticImages.size > 1 ? 's' : ''} `))}` });
|
||||
|
||||
const inputFiles = new Set<string>();
|
||||
|
||||
// process transforms one original image file at a time
|
||||
for await (const [src, transformsMap] of staticImages) {
|
||||
for (const [src, transformsMap] of staticImages) {
|
||||
let inputFile: string | undefined = undefined;
|
||||
let inputBuffer: Buffer | undefined = undefined;
|
||||
|
||||
|
@ -35,14 +47,30 @@ export async function ssgBuild({ loader, staticImages, srcDir, outDir }: SSGBuil
|
|||
|
||||
if (!inputBuffer) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`"${src}" image could not be fetched`);
|
||||
warn({ level: logLevel, message : `"${src}" image could not be fetched` });
|
||||
continue;
|
||||
}
|
||||
|
||||
const transforms = Array.from(transformsMap.entries());
|
||||
|
||||
debug({ level: logLevel, prefix: false, message: `${green('▶')} ${src}` });
|
||||
let timeStart = performance.now();
|
||||
|
||||
if (inputFile) {
|
||||
const to = inputFile.replace(fileURLToPath(srcDir), fileURLToPath(outDir));
|
||||
await ensureDir(path.dirname(to));
|
||||
await fs.copyFile(inputFile, to);
|
||||
|
||||
const timeEnd = performance.now();
|
||||
const timeChange = getTimeStat(timeStart, timeEnd);
|
||||
const timeIncrease = `(+${timeChange})`;
|
||||
const pathRelative = inputFile.replace(fileURLToPath(srcDir), '');
|
||||
debug({ level: logLevel, prefix: false, message: ` ${cyan('└─')} ${dim(`(original) ${pathRelative}`)} ${dim(timeIncrease)}` });
|
||||
}
|
||||
|
||||
// process each transformed versiono of the
|
||||
for await (const [filename, transform] of transforms) {
|
||||
for (const [filename, transform] of transforms) {
|
||||
timeStart = performance.now();
|
||||
let outputFile: string;
|
||||
|
||||
if (isRemoteImage(src)) {
|
||||
|
@ -58,14 +86,14 @@ export async function ssgBuild({ loader, staticImages, srcDir, outDir }: SSGBuil
|
|||
ensureDir(path.dirname(outputFile));
|
||||
|
||||
await fs.writeFile(outputFile, data);
|
||||
|
||||
const timeEnd = performance.now();
|
||||
const timeChange = getTimeStat(timeStart, timeEnd);
|
||||
const timeIncrease = `(+${timeChange})`;
|
||||
const pathRelative = outputFile.replace(fileURLToPath(outDir), '');
|
||||
debug({ level: logLevel, prefix: false, message: ` ${cyan('└─')} ${dim(pathRelative)} ${dim(timeIncrease)}` });
|
||||
}
|
||||
}
|
||||
|
||||
// copy all original local images to dist
|
||||
for await (const original of inputFiles) {
|
||||
const to = original.replace(fileURLToPath(srcDir), fileURLToPath(outDir));
|
||||
|
||||
await ensureDir(path.dirname(to));
|
||||
await fs.copyFile(original, to);
|
||||
}
|
||||
info({ level: logLevel, prefix: false, message: (dim(`Completed in ${getTimeStat(timer, performance.now())}.\n`)) });
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { ssgBuild } from './build/ssg.js';
|
|||
import { ssrBuild } from './build/ssr.js';
|
||||
import { PKG_NAME, ROUTE_PATTERN } from './constants.js';
|
||||
import { ImageService, TransformOptions } from './loaders/index.js';
|
||||
import type { LoggerLevel } from './utils/logger.js';
|
||||
import { filenameFormat, propsToFilename } from './utils/paths.js';
|
||||
import { createPlugin } from './vite-plugin-astro-image.js';
|
||||
|
||||
|
@ -27,11 +28,13 @@ export interface IntegrationOptions {
|
|||
* Entry point for the @type {HostedImageService} or @type {LocalImageService} to be used.
|
||||
*/
|
||||
serviceEntryPoint?: string;
|
||||
logLevel?: LoggerLevel;
|
||||
}
|
||||
|
||||
export default function integration(options: IntegrationOptions = {}): AstroIntegration {
|
||||
const resolvedOptions = {
|
||||
serviceEntryPoint: '@astrojs/image/sharp',
|
||||
logLevel: 'info' as LoggerLevel,
|
||||
...options,
|
||||
};
|
||||
|
||||
|
@ -72,7 +75,7 @@ export default function integration(options: IntegrationOptions = {}): AstroInte
|
|||
});
|
||||
}
|
||||
},
|
||||
'astro:server:setup': async () => {
|
||||
'astro:server:setup': async ({ server }) => {
|
||||
globalThis.astroImage = {};
|
||||
},
|
||||
'astro:build:setup': () => {
|
||||
|
@ -107,7 +110,7 @@ export default function integration(options: IntegrationOptions = {}): AstroInte
|
|||
const loader = globalThis?.astroImage?.loader;
|
||||
|
||||
if (loader && 'transform' in loader && staticImages.size > 0) {
|
||||
await ssgBuild({ loader, staticImages, srcDir: _config.srcDir, outDir: dir });
|
||||
await ssgBuild({ loader, staticImages, srcDir: _config.srcDir, outDir: dir, logLevel: resolvedOptions.logLevel });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
74
packages/integrations/image/src/utils/logger.ts
Normal file
74
packages/integrations/image/src/utils/logger.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
// eslint-disable no-console
|
||||
import { bold, cyan, dim, green, red, yellow } from 'kleur/colors';
|
||||
|
||||
const PREFIX = '@astrojs/image';
|
||||
|
||||
// Hey, locales are pretty complicated! Be careful modifying this logic...
|
||||
// If we throw at the top-level, international users can't use Astro.
|
||||
//
|
||||
// Using `[]` sets the default locale properly from the system!
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#parameters
|
||||
//
|
||||
// Here be the dragons we've slain:
|
||||
// https://github.com/withastro/astro/issues/2625
|
||||
// https://github.com/withastro/astro/issues/3309
|
||||
const dateTimeFormat = new Intl.DateTimeFormat([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
});
|
||||
|
||||
export type LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; // same as Pino
|
||||
|
||||
export interface LogMessage {
|
||||
level: LoggerLevel;
|
||||
message: string;
|
||||
prefix?: boolean;
|
||||
timestamp?: boolean;
|
||||
}
|
||||
|
||||
export const levels: Record<LoggerLevel, number> = {
|
||||
debug: 20,
|
||||
info: 30,
|
||||
warn: 40,
|
||||
error: 50,
|
||||
silent: 90,
|
||||
};
|
||||
|
||||
function getPrefix(level: LoggerLevel, timestamp: boolean) {
|
||||
let prefix = '';
|
||||
|
||||
if (timestamp) {
|
||||
prefix += dim(dateTimeFormat.format(new Date()) + ' ');
|
||||
}
|
||||
|
||||
switch (level) {
|
||||
case 'debug':
|
||||
prefix += bold(green(`[${PREFIX}] `));
|
||||
break;
|
||||
case 'info':
|
||||
prefix += bold(cyan(`[${PREFIX}] `));
|
||||
break;
|
||||
case 'warn':
|
||||
prefix += bold(yellow(`[${PREFIX}] `));
|
||||
break;
|
||||
case 'error':
|
||||
prefix += bold(red(`[${PREFIX}] `));
|
||||
break;
|
||||
}
|
||||
|
||||
return prefix;
|
||||
}
|
||||
|
||||
const log = (_level: LoggerLevel, dest: (message: string) => void) =>
|
||||
({ message, level, prefix = true, timestamp = true }: LogMessage) => {
|
||||
if (levels[_level] >= levels[level]) {
|
||||
dest(`${prefix ? getPrefix(level, timestamp) : ''}${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const info = log('info', console.info);
|
||||
export const debug = log('debug', console.debug);
|
||||
export const warn = log('warn', console.warn);
|
||||
export const error = log('error', console.error);
|
||||
|
|
@ -4,5 +4,5 @@ import image from '@astrojs/image';
|
|||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: 'http://localhost:3000',
|
||||
integrations: [image()]
|
||||
integrations: [image({ logLevel: 'silent' })]
|
||||
});
|
||||
|
|
|
@ -4,5 +4,5 @@ import image from '@astrojs/image';
|
|||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: 'http://localhost:3000',
|
||||
integrations: [image()]
|
||||
integrations: [image({ logLevel: 'silent' })]
|
||||
});
|
||||
|
|
|
@ -4,5 +4,5 @@ import image from '@astrojs/image';
|
|||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: 'http://localhost:3000',
|
||||
integrations: [image()]
|
||||
integrations: [image({ logLevel: 'silent' })]
|
||||
});
|
||||
|
|
|
@ -2192,6 +2192,7 @@ importers:
|
|||
astro-scripts: workspace:*
|
||||
etag: ^1.8.1
|
||||
image-size: ^1.0.1
|
||||
kleur: ^4.1.4
|
||||
mrmime: ^1.0.0
|
||||
sharp: ^0.30.6
|
||||
slash: ^4.0.0
|
||||
|
@ -2208,6 +2209,7 @@ importers:
|
|||
'@types/sharp': 0.30.5
|
||||
astro: link:../../astro
|
||||
astro-scripts: link:../../../scripts
|
||||
kleur: 4.1.5
|
||||
|
||||
packages/integrations/image/test/fixtures/basic-image:
|
||||
specifiers:
|
||||
|
|
Loading…
Reference in a new issue