2022-07-01 20:06:01 +00:00
|
|
|
import type { AstroConfig, AstroIntegration } from 'astro';
|
2022-07-01 15:47:48 +00:00
|
|
|
import fs from 'fs/promises';
|
|
|
|
import path from 'path';
|
2022-07-01 20:06:01 +00:00
|
|
|
import { fileURLToPath } from 'url';
|
2022-07-08 21:37:55 +00:00
|
|
|
import { OUTPUT_DIR, PKG_NAME, ROUTE_PATTERN } from './constants.js';
|
2022-07-18 19:43:40 +00:00
|
|
|
import sharp from './loaders/sharp.js';
|
2022-07-08 21:37:55 +00:00
|
|
|
import { IntegrationOptions, TransformOptions } from './types.js';
|
2022-07-01 17:43:26 +00:00
|
|
|
import {
|
|
|
|
ensureDir,
|
|
|
|
isRemoteImage,
|
|
|
|
loadLocalImage,
|
|
|
|
loadRemoteImage,
|
|
|
|
propsToFilename,
|
|
|
|
} from './utils.js';
|
2022-07-01 15:47:48 +00:00
|
|
|
import { createPlugin } from './vite-plugin-astro-image.js';
|
2022-07-08 21:40:22 +00:00
|
|
|
export * from './get-image.js';
|
|
|
|
export * from './get-picture.js';
|
2022-07-01 15:47:48 +00:00
|
|
|
|
|
|
|
const createIntegration = (options: IntegrationOptions = {}): AstroIntegration => {
|
|
|
|
const resolvedOptions = {
|
|
|
|
serviceEntryPoint: '@astrojs/image/sharp',
|
2022-07-01 17:43:26 +00:00
|
|
|
...options,
|
2022-07-01 15:47:48 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// During SSG builds, this is used to track all transformed images required.
|
|
|
|
const staticImages = new Map<string, TransformOptions>();
|
|
|
|
|
|
|
|
let _config: AstroConfig;
|
|
|
|
|
|
|
|
function getViteConfiguration() {
|
|
|
|
return {
|
2022-07-01 17:43:26 +00:00
|
|
|
plugins: [createPlugin(_config, resolvedOptions)],
|
2022-07-01 19:56:43 +00:00
|
|
|
optimizeDeps: {
|
2022-07-01 20:06:01 +00:00
|
|
|
include: ['image-size', 'sharp'],
|
|
|
|
},
|
2022-07-08 20:20:57 +00:00
|
|
|
ssr: {
|
|
|
|
noExternal: ['@astrojs/image'],
|
|
|
|
},
|
2022-07-01 20:06:01 +00:00
|
|
|
};
|
2022-07-01 15:47:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
name: PKG_NAME,
|
|
|
|
hooks: {
|
|
|
|
'astro:config:setup': ({ command, config, injectRoute, updateConfig }) => {
|
|
|
|
_config = config;
|
|
|
|
|
|
|
|
// Always treat `astro dev` as SSR mode, even without an adapter
|
|
|
|
const mode = command === 'dev' || config.adapter ? 'ssr' : 'ssg';
|
|
|
|
|
|
|
|
updateConfig({ vite: getViteConfiguration() });
|
|
|
|
|
|
|
|
// Used to cache all images rendered to HTML
|
|
|
|
// Added to globalThis to share the same map in Node and Vite
|
2022-07-18 19:43:40 +00:00
|
|
|
function addStaticImage(transform: TransformOptions) {
|
2022-07-01 15:47:48 +00:00
|
|
|
staticImages.set(propsToFilename(transform), transform);
|
2022-07-01 17:43:26 +00:00
|
|
|
};
|
2022-07-01 15:47:48 +00:00
|
|
|
|
|
|
|
// TODO: Add support for custom, user-provided filename format functions
|
2022-07-18 19:43:40 +00:00
|
|
|
function filenameFormat(
|
2022-07-01 17:43:26 +00:00
|
|
|
transform: TransformOptions,
|
|
|
|
searchParams: URLSearchParams
|
2022-07-18 19:43:40 +00:00
|
|
|
) {
|
2022-07-01 15:47:48 +00:00
|
|
|
if (mode === 'ssg') {
|
|
|
|
return isRemoteImage(transform.src)
|
|
|
|
? path.join(OUTPUT_DIR, path.basename(propsToFilename(transform)))
|
2022-07-01 17:43:26 +00:00
|
|
|
: path.join(
|
|
|
|
OUTPUT_DIR,
|
|
|
|
path.dirname(transform.src),
|
|
|
|
path.basename(propsToFilename(transform))
|
|
|
|
);
|
2022-07-01 15:47:48 +00:00
|
|
|
} else {
|
|
|
|
return `${ROUTE_PATTERN}?${searchParams.toString()}`;
|
|
|
|
}
|
2022-07-01 17:43:26 +00:00
|
|
|
};
|
2022-07-01 15:47:48 +00:00
|
|
|
|
2022-07-18 19:43:40 +00:00
|
|
|
// Initialize the integration's globalThis namespace
|
|
|
|
// This is needed to share scope between Node and Vite
|
|
|
|
globalThis.astroImage = {
|
|
|
|
loader: undefined, // initialized in first getImage() call
|
|
|
|
ssrLoader: sharp,
|
|
|
|
command,
|
|
|
|
addStaticImage,
|
|
|
|
filenameFormat,
|
|
|
|
}
|
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
if (mode === 'ssr') {
|
|
|
|
injectRoute({
|
|
|
|
pattern: ROUTE_PATTERN,
|
2022-07-01 17:43:26 +00:00
|
|
|
entryPoint:
|
|
|
|
command === 'dev' ? '@astrojs/image/endpoints/dev' : '@astrojs/image/endpoints/prod',
|
2022-07-01 15:47:48 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'astro:build:done': async ({ dir }) => {
|
|
|
|
for await (const [filename, transform] of staticImages) {
|
2022-07-18 19:43:40 +00:00
|
|
|
const loader = globalThis.astroImage.loader;
|
|
|
|
|
|
|
|
if (!loader || !('transform' in loader)) {
|
|
|
|
// this should never be hit, how was a staticImage added without an SSR service?
|
|
|
|
return;
|
|
|
|
}
|
2022-07-01 15:47:48 +00:00
|
|
|
|
|
|
|
let inputBuffer: Buffer | undefined = undefined;
|
|
|
|
let outputFile: string;
|
|
|
|
|
|
|
|
if (isRemoteImage(transform.src)) {
|
|
|
|
// try to load the remote image
|
|
|
|
inputBuffer = await loadRemoteImage(transform.src);
|
|
|
|
|
2022-07-01 17:43:26 +00:00
|
|
|
const outputFileURL = new URL(
|
|
|
|
path.join('./', OUTPUT_DIR, path.basename(filename)),
|
|
|
|
dir
|
|
|
|
);
|
2022-07-01 15:47:48 +00:00
|
|
|
outputFile = fileURLToPath(outputFileURL);
|
|
|
|
} else {
|
|
|
|
const inputFileURL = new URL(`.${transform.src}`, _config.srcDir);
|
|
|
|
const inputFile = fileURLToPath(inputFileURL);
|
|
|
|
inputBuffer = await loadLocalImage(inputFile);
|
|
|
|
|
|
|
|
const outputFileURL = new URL(path.join('./', OUTPUT_DIR, filename), dir);
|
|
|
|
outputFile = fileURLToPath(outputFileURL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!inputBuffer) {
|
2022-07-07 21:06:44 +00:00
|
|
|
// eslint-disable-next-line no-console
|
2022-07-01 15:47:48 +00:00
|
|
|
console.warn(`"${transform.src}" image could not be fetched`);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { data } = await loader.transform(inputBuffer, transform);
|
|
|
|
ensureDir(path.dirname(outputFile));
|
|
|
|
await fs.writeFile(outputFile, data);
|
|
|
|
}
|
2022-07-01 17:43:26 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
};
|
2022-07-01 15:47:48 +00:00
|
|
|
|
|
|
|
export default createIntegration;
|