209 lines
7.6 KiB
JavaScript
209 lines
7.6 KiB
JavaScript
![]() |
import { bold } from "kleur/colors";
|
||
|
import MagicString from "magic-string";
|
||
|
import mime from "mime/lite.js";
|
||
|
import fs from "node:fs/promises";
|
||
|
import { Readable } from "node:stream";
|
||
|
import { fileURLToPath } from "node:url";
|
||
|
import { normalizePath } from "vite";
|
||
|
import { error } from "../core/logger/core.js";
|
||
|
import {
|
||
|
appendForwardSlash,
|
||
|
joinPaths,
|
||
|
prependForwardSlash,
|
||
|
removeQueryString
|
||
|
} from "../core/path.js";
|
||
|
import { VIRTUAL_MODULE_ID, VIRTUAL_SERVICE_ID } from "./consts.js";
|
||
|
import { isESMImportedImage } from "./internal.js";
|
||
|
import { isLocalService } from "./services/service.js";
|
||
|
import { emitESMImage } from "./utils/emitAsset.js";
|
||
|
import { imageMetadata } from "./utils/metadata.js";
|
||
|
import { getOrigQueryParams } from "./utils/queryParams.js";
|
||
|
import { hashTransform, propsToFilename } from "./utils/transformToPath.js";
|
||
|
const resolvedVirtualModuleId = "\0" + VIRTUAL_MODULE_ID;
|
||
|
const rawRE = /(?:\?|&)raw(?:&|$)/;
|
||
|
const urlRE = /(\?|&)url(?:&|$)/;
|
||
|
function assets({
|
||
|
settings,
|
||
|
logging,
|
||
|
mode
|
||
|
}) {
|
||
|
var _a;
|
||
|
let resolvedConfig;
|
||
|
globalThis.astroAsset = {};
|
||
|
const UNSUPPORTED_ADAPTERS = /* @__PURE__ */ new Set([
|
||
|
"@astrojs/cloudflare",
|
||
|
"@astrojs/deno",
|
||
|
"@astrojs/netlify/edge-functions",
|
||
|
"@astrojs/vercel/edge"
|
||
|
]);
|
||
|
const adapterName = (_a = settings.config.adapter) == null ? void 0 : _a.name;
|
||
|
if (["astro/assets/services/sharp", "astro/assets/services/squoosh"].includes(
|
||
|
settings.config.image.service.entrypoint
|
||
|
) && adapterName && UNSUPPORTED_ADAPTERS.has(adapterName)) {
|
||
|
error(
|
||
|
logging,
|
||
|
"assets",
|
||
|
`The currently selected adapter \`${adapterName}\` does not run on Node, however the currently used image service depends on Node built-ins. ${bold(
|
||
|
"Your project will NOT be able to build."
|
||
|
)}`
|
||
|
);
|
||
|
}
|
||
|
return [
|
||
|
// Expose the components and different utilities from `astro:assets` and handle serving images from `/_image` in dev
|
||
|
{
|
||
|
name: "astro:assets",
|
||
|
config() {
|
||
|
return {
|
||
|
resolve: {
|
||
|
alias: [
|
||
|
{
|
||
|
find: /^~\/assets\/(.+)$/,
|
||
|
replacement: fileURLToPath(new URL("./assets/$1", settings.config.srcDir))
|
||
|
}
|
||
|
]
|
||
|
}
|
||
|
};
|
||
|
},
|
||
|
async resolveId(id) {
|
||
|
if (id === VIRTUAL_SERVICE_ID) {
|
||
|
return await this.resolve(settings.config.image.service.entrypoint);
|
||
|
}
|
||
|
if (id === VIRTUAL_MODULE_ID) {
|
||
|
return resolvedVirtualModuleId;
|
||
|
}
|
||
|
},
|
||
|
load(id) {
|
||
|
if (id === resolvedVirtualModuleId) {
|
||
|
return `
|
||
|
export { getConfiguredImageService, isLocalService } from "astro/assets";
|
||
|
import { getImage as getImageInternal } from "astro/assets";
|
||
|
export { default as Image } from "astro/components/Image.astro";
|
||
|
|
||
|
export const imageServiceConfig = ${JSON.stringify(settings.config.image.service.config)};
|
||
|
export const getImage = async (options) => await getImageInternal(options, imageServiceConfig);
|
||
|
`;
|
||
|
}
|
||
|
},
|
||
|
// Handle serving images during development
|
||
|
configureServer(server) {
|
||
|
server.middlewares.use(async (req, res, next) => {
|
||
|
var _a2, _b;
|
||
|
if ((_a2 = req.url) == null ? void 0 : _a2.startsWith("/_image")) {
|
||
|
if (!isLocalService(globalThis.astroAsset.imageService)) {
|
||
|
return next();
|
||
|
}
|
||
|
const url = new URL(req.url, "file:");
|
||
|
if (!url.searchParams.has("href")) {
|
||
|
return next();
|
||
|
}
|
||
|
const filePath = (_b = url.searchParams.get("href")) == null ? void 0 : _b.slice("/@fs".length);
|
||
|
const filePathURL = new URL("." + filePath, "file:");
|
||
|
const file = await fs.readFile(filePathURL);
|
||
|
let meta = getOrigQueryParams(filePathURL.searchParams);
|
||
|
if (!meta) {
|
||
|
meta = await imageMetadata(filePathURL, file);
|
||
|
if (!meta) {
|
||
|
return next();
|
||
|
}
|
||
|
}
|
||
|
const transform = await globalThis.astroAsset.imageService.parseURL(
|
||
|
url,
|
||
|
settings.config.image.service.config
|
||
|
);
|
||
|
if (transform === void 0) {
|
||
|
error(logging, "image", `Failed to parse transform for ${url}`);
|
||
|
}
|
||
|
let data = file;
|
||
|
let format = meta.format;
|
||
|
if (transform) {
|
||
|
const result = await globalThis.astroAsset.imageService.transform(
|
||
|
file,
|
||
|
transform,
|
||
|
settings.config.image.service.config
|
||
|
);
|
||
|
data = result.data;
|
||
|
format = result.format;
|
||
|
}
|
||
|
res.setHeader("Content-Type", mime.getType(format) ?? `image/${format}`);
|
||
|
res.setHeader("Cache-Control", "max-age=360000");
|
||
|
const stream = Readable.from(data);
|
||
|
return stream.pipe(res);
|
||
|
}
|
||
|
return next();
|
||
|
});
|
||
|
},
|
||
|
buildStart() {
|
||
|
if (mode != "build") {
|
||
|
return;
|
||
|
}
|
||
|
globalThis.astroAsset.addStaticImage = (options) => {
|
||
|
if (!globalThis.astroAsset.staticImages) {
|
||
|
globalThis.astroAsset.staticImages = /* @__PURE__ */ new Map();
|
||
|
}
|
||
|
const hash = hashTransform(options, settings.config.image.service.entrypoint);
|
||
|
let filePath;
|
||
|
if (globalThis.astroAsset.staticImages.has(hash)) {
|
||
|
filePath = globalThis.astroAsset.staticImages.get(hash).path;
|
||
|
} else {
|
||
|
if (!isESMImportedImage(options.src)) {
|
||
|
return options.src;
|
||
|
}
|
||
|
filePath = prependForwardSlash(
|
||
|
joinPaths(settings.config.build.assets, propsToFilename(options, hash))
|
||
|
);
|
||
|
globalThis.astroAsset.staticImages.set(hash, { path: filePath, options });
|
||
|
}
|
||
|
if (settings.config.build.assetsPrefix) {
|
||
|
return joinPaths(settings.config.build.assetsPrefix, filePath);
|
||
|
} else {
|
||
|
return prependForwardSlash(joinPaths(settings.config.base, filePath));
|
||
|
}
|
||
|
};
|
||
|
},
|
||
|
// In build, rewrite paths to ESM imported images in code to their final location
|
||
|
async renderChunk(code) {
|
||
|
const assetUrlRE = /__ASTRO_ASSET_IMAGE__([a-z\d]{8})__(?:_(.*?)__)?/g;
|
||
|
let match;
|
||
|
let s;
|
||
|
while (match = assetUrlRE.exec(code)) {
|
||
|
s = s || (s = new MagicString(code));
|
||
|
const [full, hash, postfix = ""] = match;
|
||
|
const file = this.getFileName(hash);
|
||
|
const prefix = settings.config.build.assetsPrefix ? appendForwardSlash(settings.config.build.assetsPrefix) : resolvedConfig.base;
|
||
|
const outputFilepath = prefix + normalizePath(file + postfix);
|
||
|
s.overwrite(match.index, match.index + full.length, outputFilepath);
|
||
|
}
|
||
|
if (s) {
|
||
|
return {
|
||
|
code: s.toString(),
|
||
|
map: resolvedConfig.build.sourcemap ? s.generateMap({ hires: true }) : null
|
||
|
};
|
||
|
} else {
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
// Return a more advanced shape for images imported in ESM
|
||
|
{
|
||
|
name: "astro:assets:esm",
|
||
|
enforce: "pre",
|
||
|
configResolved(viteConfig) {
|
||
|
resolvedConfig = viteConfig;
|
||
|
},
|
||
|
async load(id) {
|
||
|
if (rawRE.test(id) || urlRE.test(id)) {
|
||
|
return;
|
||
|
}
|
||
|
const cleanedUrl = removeQueryString(id);
|
||
|
if (/\.(jpeg|jpg|png|tiff|webp|gif|svg)$/.test(cleanedUrl)) {
|
||
|
const meta = await emitESMImage(id, this.meta.watchMode, this.emitFile);
|
||
|
return `export default ${JSON.stringify(meta)}`;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
];
|
||
|
}
|
||
|
export {
|
||
|
assets as default
|
||
|
};
|