UNPKG

12.4 kBJavaScriptView Raw
1"use strict";
2var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 return new (P || (P = Promise))(function (resolve, reject) {
5 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 step((generator = generator.apply(thisArg, _arguments || [])).next());
9 });
10};
11var __rest = (this && this.__rest) || function (s, e) {
12 var t = {};
13 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
14 t[p] = s[p];
15 if (s != null && typeof Object.getOwnPropertySymbols === "function")
16 for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
17 if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
18 t[p[i]] = s[p[i]];
19 }
20 return t;
21};
22var __importDefault = (this && this.__importDefault) || function (mod) {
23 return (mod && mod.__esModule) ? mod : { "default": mod };
24};
25Object.defineProperty(exports, "__esModule", { value: true });
26const image_utils_1 = require("@expo/image-utils");
27const fs_extra_1 = __importDefault(require("fs-extra"));
28const mime_1 = __importDefault(require("mime"));
29const node_fetch_1 = __importDefault(require("node-fetch"));
30const path_1 = __importDefault(require("path"));
31const stream_1 = __importDefault(require("stream"));
32const tempy_1 = __importDefault(require("tempy"));
33const util_1 = __importDefault(require("util"));
34const chalk_1 = __importDefault(require("chalk"));
35const crypto_1 = __importDefault(require("crypto"));
36const Errors_1 = require("./Errors");
37const utils_1 = require("./utils");
38const Apple_1 = require("./validators/Apple");
39const supportedMimeTypes = ['image/png', 'image/jpeg', 'image/webp'];
40function sanitizeIcon(iconSnippet) {
41 if (!iconSnippet.src) {
42 throw new Errors_1.IconError('Unknown icon source.');
43 }
44 const sizes = utils_1.toArray(iconSnippet.size || iconSnippet.sizes);
45 if (!sizes) {
46 throw new Errors_1.IconError('Unknown icon sizes.');
47 }
48 return {
49 src: iconSnippet.src,
50 resizeMode: iconSnippet.resizeMode,
51 sizes,
52 media: iconSnippet.media,
53 destination: iconSnippet.destination,
54 ios: iconSnippet.ios,
55 color: iconSnippet.color,
56 };
57}
58function getBufferWithMimeAsync({ src, resizeMode, color }, mimeType, { width, height }) {
59 return __awaiter(this, void 0, void 0, function* () {
60 let imagePath;
61 if (!supportedMimeTypes.includes(mimeType)) {
62 imagePath = src;
63 }
64 else {
65 let localSrc = src;
66 // In case the icon is a remote URL we need to download it first
67 if (src.startsWith('http')) {
68 localSrc = yield downloadImage(src);
69 }
70 imagePath = yield resize(localSrc, mimeType, width, height, resizeMode, color);
71 }
72 try {
73 return yield fs_extra_1.default.readFile(imagePath);
74 }
75 catch (err) {
76 throw new Errors_1.IconError(`It was not possible to read '${src}'.`);
77 }
78 });
79}
80function downloadImage(url) {
81 return __awaiter(this, void 0, void 0, function* () {
82 const outputPath = tempy_1.default.directory();
83 const localPath = path_1.default.join(outputPath, path_1.default.basename(url));
84 const response = yield node_fetch_1.default(url);
85 if (!response.ok) {
86 throw new Errors_1.IconError(`It was not possible to download splash screen from '${url}'`);
87 }
88 // Download to local file
89 const streamPipeline = util_1.default.promisify(stream_1.default.pipeline);
90 yield streamPipeline(response.body, fs_extra_1.default.createWriteStream(localPath));
91 return localPath;
92 });
93}
94function ensureCacheDirectory(projectRoot, cacheKey) {
95 return __awaiter(this, void 0, void 0, function* () {
96 const cacheFolder = path_1.default.join(projectRoot, '.expo/web/cache/production/images', cacheKey);
97 yield fs_extra_1.default.ensureDir(cacheFolder);
98 return cacheFolder;
99 });
100}
101function getImageFromCacheAsync(fileName, cacheKey) {
102 return __awaiter(this, void 0, void 0, function* () {
103 try {
104 return yield fs_extra_1.default.readFile(path_1.default.resolve(cacheKeys[cacheKey], fileName));
105 }
106 catch (_) {
107 return null;
108 }
109 });
110}
111function cacheImageAsync(fileName, buffer, cacheKey) {
112 return __awaiter(this, void 0, void 0, function* () {
113 try {
114 yield fs_extra_1.default.writeFile(path_1.default.resolve(cacheKeys[cacheKey], fileName), buffer);
115 }
116 catch ({ message }) {
117 console.warn(`error caching image: "${fileName}". ${message}`);
118 }
119 });
120}
121function processImageAsync(size, icon, publicPath, cacheKey) {
122 return __awaiter(this, void 0, void 0, function* () {
123 const { width, height } = utils_1.toSize(size);
124 if (width <= 0 || height <= 0) {
125 throw Error(`Failed to process image with invalid size: { width: ${width}, height: ${height}}`);
126 }
127 const mimeType = mime_1.default.getType(icon.src);
128 if (!mimeType) {
129 throw new Error(`Invalid mimeType for image with source: ${icon.src}`);
130 }
131 const dimensions = `${width}x${height}`;
132 const fileName = `icon_${dimensions}.${mime_1.default.getExtension(mimeType)}`;
133 let imageBuffer = yield getImageFromCacheAsync(fileName, cacheKey);
134 if (!imageBuffer) {
135 imageBuffer = yield getBufferWithMimeAsync(icon, mimeType, { width, height });
136 yield cacheImageAsync(fileName, imageBuffer, cacheKey);
137 }
138 const iconOutputDir = icon.destination ? utils_1.joinURI(icon.destination, fileName) : fileName;
139 const iconPublicUrl = utils_1.joinURI(publicPath, iconOutputDir);
140 return {
141 manifestIcon: {
142 src: iconPublicUrl,
143 sizes: dimensions,
144 type: mimeType,
145 },
146 webpackAsset: {
147 output: iconOutputDir,
148 url: iconPublicUrl,
149 source: imageBuffer,
150 size: imageBuffer.length,
151 ios: icon.ios
152 ? { valid: icon.ios, media: icon.media, size: dimensions, href: iconPublicUrl }
153 : false,
154 resizeMode: icon.resizeMode,
155 color: icon.color,
156 },
157 };
158 });
159}
160function ensureValidMimeType(mimeType) {
161 if (['input', 'jpeg', 'jpg', 'png', 'raw', 'tiff', 'webp'].includes(mimeType)) {
162 return mimeType;
163 }
164 return 'png';
165}
166function resize(inputPath, mimeType, width, height, fit = 'contain', background) {
167 return __awaiter(this, void 0, void 0, function* () {
168 const format = ensureValidMimeType(mimeType.split('/')[1]);
169 try {
170 const outputPath = tempy_1.default.directory();
171 yield image_utils_1.sharpAsync({
172 input: inputPath,
173 output: outputPath,
174 format,
175 }, [
176 {
177 operation: 'flatten',
178 background,
179 },
180 {
181 operation: 'resize',
182 width,
183 height,
184 fit,
185 background,
186 },
187 ]);
188 return path_1.default.join(outputPath, path_1.default.basename(inputPath));
189 }
190 catch ({ message }) {
191 throw new Errors_1.IconError(`It was not possible to generate splash screen '${inputPath}'. ${message}`);
192 }
193 });
194}
195function retrieveIcons(manifest) {
196 // Remove these items so they aren't written to disk.
197 const { startupImages, icons } = manifest, config = __rest(manifest, ["startupImages", "icons"]);
198 const parsedStartupImages = utils_1.toArray(startupImages);
199 let parsedIcons = utils_1.toArray(icons);
200 if (parsedStartupImages.length) {
201 // TODO: Bacon: use all of the startup images
202 const startupImage = parsedStartupImages[0];
203 parsedIcons = [...parsedIcons, ...Apple_1.fromStartupImage(startupImage)];
204 }
205 const response = parsedIcons.map(icon => sanitizeIcon(icon));
206 return [response, config];
207}
208exports.retrieveIcons = retrieveIcons;
209// Calculate SHA256 Checksum value of a file based on its contents
210function calculateHash(filePath) {
211 const contents = fs_extra_1.default.readFileSync(filePath);
212 return crypto_1.default
213 .createHash('sha256')
214 .update(contents)
215 .digest('hex');
216}
217// Create a hash key for caching the images between builds
218function createCacheKey(icon) {
219 const hash = calculateHash(icon.src);
220 return [hash, icon.resizeMode, icon.color].filter(Boolean).join('-');
221}
222const cacheKeys = {};
223function parseIconsAsync(projectRoot, inputIcons, publicPath) {
224 return __awaiter(this, void 0, void 0, function* () {
225 if (!inputIcons.length) {
226 return {};
227 }
228 if (!(yield image_utils_1.isAvailableAsync())) {
229 // TODO: Bacon: Fallback to nodejs image resizing as native doesn't work in the host environment.
230 console.log();
231 console.log(chalk_1.default.bgRed.black(`PWA Error: It was not possible to generate the images for your progressive web app (splash screens and icons) because the host computer does not have \`sharp\` installed, and \`image-utils\` was unable to install it automatically.`));
232 console.log(chalk_1.default.red(`- You may stop the process and try again after successfully running \`npm install -g sharp\`.\n- If you are using \`expo-cli\` to build your project then you could use the \`--no-pwa\` flag to skip the PWA asset generation step entirely.`));
233 return {};
234 }
235 const icons = [];
236 const assets = [];
237 let promises = [];
238 for (const icon of inputIcons) {
239 const cacheKey = createCacheKey(icon);
240 if (!(cacheKey in cacheKeys)) {
241 cacheKeys[cacheKey] = yield ensureCacheDirectory(projectRoot, cacheKey);
242 }
243 const { sizes } = icon;
244 promises = [
245 ...promises,
246 ...sizes.map((size) => __awaiter(this, void 0, void 0, function* () {
247 const { manifestIcon, webpackAsset } = yield processImageAsync(size, icon, publicPath, cacheKey);
248 icons.push(manifestIcon);
249 assets.push(webpackAsset);
250 })),
251 ];
252 }
253 yield Promise.all(promises);
254 yield clearUnusedCachesAsync(projectRoot);
255 return {
256 icons: sortByAttribute(icons, 'sizes'),
257 // startupImages: icons.filter(({ isStartupImage }) => isStartupImage),
258 assets: sortByAttribute(assets, 'output'),
259 };
260 });
261}
262exports.parseIconsAsync = parseIconsAsync;
263function sortByAttribute(arr, key) {
264 return arr.filter(Boolean).sort((valueA, valueB) => {
265 if (valueA[key] < valueB[key])
266 return -1;
267 else if (valueA[key] > valueB[key])
268 return 1;
269 return 0;
270 });
271}
272function clearUnusedCachesAsync(projectRoot) {
273 return __awaiter(this, void 0, void 0, function* () {
274 // Clean up any old caches
275 const cacheFolder = path_1.default.join(projectRoot, '.expo/web/cache/production/images');
276 const currentCaches = yield fs_extra_1.default.readdir(cacheFolder);
277 const deleteCachePromises = [];
278 for (const cache of currentCaches) {
279 // skip hidden folders
280 if (cache.startsWith('.')) {
281 continue;
282 }
283 // delete
284 if (!(cache in cacheKeys)) {
285 deleteCachePromises.push(fs_extra_1.default.remove(path_1.default.join(cacheFolder, cache)));
286 }
287 }
288 yield Promise.all(deleteCachePromises);
289 });
290}
291//# sourceMappingURL=icons.js.map
\No newline at end of file