UNPKG

react-native-svg-asset-plugin

Version:
218 lines (217 loc) 7.68 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; const node_path_1 = __importDefault(require("node:path")); const fs_extra_1 = __importDefault(require("fs-extra")); const cache = __importStar(require("./cache")); const config = __importStar(require("./config")); const sharp = __importStar(require("./sharp")); const fsUtils = __importStar(require("./utils/fs")); const funcUtils = __importStar(require("./utils/func")); const asyncConfig = config.load(); /** * Main function called by the metro bundler when processing an asset. This * plugin renders SVG assets into PNG files that can be used natively by * React Native apps. * * Note that metro caches the results of this function, so that multiple uses * of the same asset will not cause multiple calls to this function. This also * means that return values for old versions of a source file might be cached, * leading to metro expecting previously rendered PNG files to exist for * example when undoing changes to a source SVG file. * * @param assetData Data on the required asset provided by the metro bundler. * See below for examples. * @returns Modified asset data that points to the rendered PNG files. */ async function reactNativeSvgAssetPlugin( /** * @example // Metro and React Native * { * __packager_asset: true, * fileSystemLocation: '/src/app/images', * httpServerLocation: '/assets/images', * width: 100, * height: 100, * scales: [ 1 ], * files: [ '/src/app/images/icon.svg' ], * hash: '2f361af1b5dee4c16c567c61d8623df0', * name: 'icon', * type: 'svg' * } * * @example // Expo dev build * { * __packager_asset: true, * fileSystemLocation: '/src/app/images', * httpServerLocation: '/assets/?unstable_path=./images', * width: 200, * height: 200, * scales: [ 1 ], * files: [ '/src/app/images/icon.svg' ], * hash: '2f361af1b5dee4c16c567c61d8623df0', * name: 'icon', * type: 'svg' * } * * @example // Expo production build * { * __packager_asset: true, * fileSystemLocation: '/src/app/images', * httpServerLocation: '/assets?export_path=/assets/images', * width: 200, * height: 200, * scales: [ 1 ], * files: [ '/src/app/images/icon.svg' ], * hash: '2f361af1b5dee4c16c567c61d8623df0', * name: 'icon', * type: 'svg' * } */ assetData) { const filePath = assetData.files[0] || ""; if (await shouldConvertFile(assetData, filePath)) { return convertSvg(assetData); } else { return assetData; } } async function shouldConvertFile(assetData, filePath) { if (assetData.type !== "svg") { return false; } const ignoreRegex = (await asyncConfig).ignoreRegex; if (ignoreRegex?.test(filePath)) { return false; } return true; } async function convertSvg(assetData) { if (assetData.scales.length !== assetData.files.length) { throw new Error("Passed scales doesn't match passed files."); } else if (assetData.files[0] === undefined) { throw new Error("No files passed."); } else if (assetData.files.length > 1) { throw new Error("Multiple SVG scales not supported."); } else if (assetData.scales[0] !== 1) { throw new Error("Scaled SVGs not supported."); } const inputFilePath = assetData.files[0]; const inputFileScale = assetData.scales[0]; const config = await asyncConfig; // Get the output directory where generated assets should be placed const outputDirectory = await cache.getCacheStoragePath(config, assetData); const outputName = `${assetData.name}-${assetData.hash}`; const imageLoader = createimageLoader(inputFilePath); const outputImages = await Promise.all(config.scales.map((imageScale) => ensurePngUpToDate(imageLoader, imageScale / inputFileScale, node_path_1.default.join(outputDirectory, `${outputName}${getScaleSuffix(imageScale)}.png`), config.output))); return { ...assetData, fileSystemLocation: outputDirectory, httpServerLocation: `${assetData.httpServerLocation}/${config.cacheDir}`, files: outputImages.map((outputImage) => outputImage.filePath), scales: outputImages.map((outputImage) => outputImage.scale), name: outputName, type: "png", }; } /** * Creates an image loader for input file. * This provides lazy cached loading of image data. */ function createimageLoader(inputFilePath) { return funcUtils.memo(async () => { const [fileBuffer, loadedSharp] = await Promise.all([ fs_extra_1.default.readFile(inputFilePath), sharp.load(), ]); const metadata = await loadedSharp(fileBuffer).metadata(); return { buffer: fileBuffer, metadata: metadata, }; }); } /** * Ensures that the resultign PNG file exists on the fileystem. * * In case the file does not exist yet, or it is older than the * current configuration, it will be generated. * * Otherwise the existing file will be left in place, and its * last modified time will be updated. */ async function ensurePngUpToDate(imageLoader, scale, outputFilePath, outputOptions) { if (await cache.isFileOutdated(outputFilePath, await asyncConfig)) { const inputFile = await imageLoader(); await generatePng(inputFile, scale, outputFilePath, outputOptions); } else { await fsUtils.updateLastModifiedTime(outputFilePath); } return { filePath: outputFilePath, scale: scale, }; } /** * Generates a PNG file from a loaded SVG file. */ async function generatePng(inputFile, scale, outputFilePath, outputOptions) { if (inputFile.metadata.density === undefined) { throw new Error("Input image missing density information"); } const density = inputFile.metadata.density; const loadedSharp = await sharp.load(); await loadedSharp(inputFile.buffer, { density: density * scale, }) .png(outputOptions) .toFile(outputFilePath); } function getScaleSuffix(scale) { switch (scale) { case 1: return ""; default: return `@${scale}x`; } } module.exports = reactNativeSvgAssetPlugin;