pwa-asset-generator
Version:
Automates PWA asset generation and image declaration. Automatically generates icon and splash screen images, favicons and mstile images. Updates manifest.json and index.html files with the generated images according to Web App Manifest specs and Apple Hum
211 lines (210 loc) • 10.2 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const constants_1 = __importDefault(require("../config/constants"));
const url_1 = __importDefault(require("./url"));
const file_1 = __importDefault(require("./file"));
const images_1 = __importDefault(require("./images"));
const browser_1 = __importDefault(require("./browser"));
const logger_1 = __importDefault(require("./logger"));
const getAppleSplashScreenData = (browser, options) => __awaiter(void 0, void 0, void 0, function* () {
const logger = logger_1.default(getAppleSplashScreenData.name, options);
const page = yield browser.newPage();
yield page.setUserAgent(constants_1.default.EMULATED_USER_AGENT);
logger.log(`Navigating to Apple Human Interface Guidelines website - ${constants_1.default.APPLE_HIG_SPLASH_SCR_SPECS_URL}`);
yield page.goto(constants_1.default.APPLE_HIG_SPLASH_SCR_SPECS_URL, {
waitUntil: 'networkidle0',
});
logger.log('Waiting for the data table to be loaded');
try {
yield page.waitForSelector('table', {
timeout: constants_1.default.WAIT_FOR_SELECTOR_TIMEOUT,
});
}
catch (e) {
logger.error(`Could not find the table on the page within timeout ${constants_1.default.WAIT_FOR_SELECTOR_TIMEOUT}ms`);
throw e;
}
const splashScreenData = yield page.evaluate(() => {
const scrapeSplashScreenDataFromHIGPage = () => {
var _a;
return Array.from((_a = document.querySelectorAll('table')) === null || _a === void 0 ? void 0 : _a[0].querySelectorAll('tbody tr')).map((tr) => {
// https://regex101.com/r/4dwvYf/4
const dimensionRegex = new RegExp(/(\d+)x(\d+)\spt\s\((\d+)x(\d+)\spx\s@(\d)x\)/gm);
const getParsedSpecs = (val) => {
const regexMatch = dimensionRegex.exec(val);
if (!(regexMatch === null || regexMatch === void 0 ? void 0 : regexMatch.length)) {
throw Error('Regex match failed while scraping the specs');
}
const widthInPoints = parseInt(regexMatch[1], 10);
const heightInPoints = parseInt(regexMatch[2], 10);
const scaleFactor = parseInt(regexMatch[5], 10);
if (widthInPoints === 0 ||
Number.isNaN(widthInPoints) ||
heightInPoints === 0 ||
Number.isNaN(heightInPoints) ||
scaleFactor === 0 ||
Number.isNaN(scaleFactor)) {
throw Error('Got unexpected dimensions while scraping the specs');
}
return {
width: widthInPoints * scaleFactor,
height: heightInPoints * scaleFactor,
scaleFactor,
};
};
const tableColumns = ['device', 'portrait'];
const columns = Array.from(tr.querySelectorAll('td'));
if (columns.length !== tableColumns.length) {
throw Error('Table columns on the page do not match with the scraper');
}
return columns.reduce((acc, curr, index) => {
if (index === 0) {
return Object.assign(Object.assign({}, acc), { device: curr.innerText });
}
const specs = getParsedSpecs(curr.innerText.trim());
return Object.assign(Object.assign({}, acc), { portrait: { width: specs.width, height: specs.height }, landscape: { width: specs.height, height: specs.width }, scaleFactor: specs.scaleFactor });
}, {
device: '',
portrait: { width: 0, height: 0 },
landscape: { width: 0, height: 0 },
scaleFactor: 0,
});
});
};
return scrapeSplashScreenDataFromHIGPage();
});
if (!splashScreenData.length) {
const err = `Failed scraping the data on web page ${constants_1.default.APPLE_HIG_SPLASH_SCR_SPECS_URL}`;
logger.error(err);
throw Error(err);
}
logger.log('Retrieved splash screen data');
yield page.close();
return splashScreenData;
});
const getSplashScreenMetaData = (options, browser) => __awaiter(void 0, void 0, void 0, function* () {
const logger = logger_1.default(getSplashScreenMetaData.name, options);
if (!options.scrape) {
logger.log(`Skipped scraping - using static data`);
return constants_1.default.APPLE_HIG_SPLASH_SCREEN_FALLBACK_DATA;
}
logger.log('Initialising puppeteer to load latest splash screen metadata', '🤖');
let splashScreenMetaData;
try {
splashScreenMetaData = yield getAppleSplashScreenData(browser, options);
logger.success('Loaded metadata for iOS platform');
}
catch (e) {
logger.error(e);
logger.warn(`Failed to fetch latest specs from Apple Human Interface guidelines - using static fallback data`);
throw e;
}
return splashScreenMetaData;
});
const canNavigateTo = (source) => (url_1.default.isUrl(source) && !file_1.default.isImageFile(source)) || file_1.default.isHtmlFile(source);
const saveImages = (imageList, source, output, options, browser) => __awaiter(void 0, void 0, void 0, function* () {
let address;
let shellHtml;
const logger = logger_1.default(saveImages.name, options);
logger.log('Initialising puppeteer to take screenshots', '🤖');
if (canNavigateTo(source)) {
address = yield url_1.default.getAddress(source, options);
}
else {
shellHtml = yield url_1.default.getShellHtml(source, options);
}
return Promise.all(imageList.map(({ name, width, height, scaleFactor, orientation }) => __awaiter(void 0, void 0, void 0, function* () {
const { quality } = options;
const type = name.includes('icon') ? 'png' : options.type;
const path = file_1.default.getImageSavePath(name, output, type);
try {
const page = yield browser.newPage();
yield page.emulate({
userAgent: constants_1.default.EMULATED_USER_AGENT,
viewport: {
width: width / scaleFactor,
height: height / scaleFactor,
deviceScaleFactor: scaleFactor,
isLandscape: orientation === 'landscape',
},
});
if (address) {
// Emulate dark mode media feature when html source is provided and darkMode is enabled
if (options.darkMode) {
yield page.emulateMediaFeatures([
{
name: 'prefers-color-scheme',
value: 'dark',
},
]);
}
yield page.goto(address, { waitUntil: 'networkidle0' });
}
else {
yield page.setContent(shellHtml);
}
yield page.screenshot(Object.assign({ path, omitBackground: !options.opaque }, (type !== 'png' ? { quality } : {})));
yield page.close();
logger.success(`Saved image ${name}`);
return { name, width, height, scaleFactor, path, orientation };
}
catch (e) {
logger.error(e.message);
throw Error(`Failed to save image ${name}`);
}
})));
});
const generateImages = (source, output, options) => __awaiter(void 0, void 0, void 0, function* () {
const logger = logger_1.default(generateImages.name, options);
const isHtmlInput = canNavigateTo(source);
if (isHtmlInput) {
logger.warn('noSandbox option is disabled for HTML inputs, use an image input instead');
}
const { browser, chrome } = yield browser_1.default.getBrowserInstance({
timeout: constants_1.default.BROWSER_TIMEOUT,
args: constants_1.default.CHROME_LAUNCH_ARGS,
}, isHtmlInput ? false : options.noSandbox);
let splashScreenMetaData;
try {
splashScreenMetaData = yield getSplashScreenMetaData(options, browser);
}
catch (e) {
splashScreenMetaData = constants_1.default.APPLE_HIG_SPLASH_SCREEN_FALLBACK_DATA;
}
const allImages = [
...(!options.iconOnly
? images_1.default.getSplashScreenImages(splashScreenMetaData, options)
: []),
...(!options.splashOnly ? images_1.default.getIconImages(options) : []),
];
if (!((yield file_1.default.exists(output)) &&
(yield file_1.default.isPathAccessible(output, file_1.default.WRITE_ACCESS)))) {
file_1.default.makeDirRecursiveSync(output);
logger.warn(`Looks like folder ${output} doesn't exist. Created one for you`);
}
const savedImages = yield saveImages(allImages, source, output, options, browser);
try {
yield browser_1.default.killBrowser(browser, chrome);
}
catch (e) {
// Silently try killing chrome as Chrome launcher might have already killed it
}
return savedImages;
});
exports.default = {
getSplashScreenMetaData,
saveImages,
generateImages,
};