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
172 lines (171 loc) • 8.31 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 cheerio_1 = __importDefault(require("cheerio"));
const mime_types_1 = require("mime-types");
const constants_1 = __importDefault(require("../config/constants"));
const file_1 = __importDefault(require("./file"));
const meta_1 = require("../models/meta");
const generateOutputPath = (options, imageName, imagePath, isManifest = false) => {
const { type, path: pathPrefix, pathOverride, index: indexHtmlPath, manifest: manifestJsonPath, } = options;
const outputFilePath = (isManifest
? manifestJsonPath
: indexHtmlPath);
if (pathOverride) {
return `${pathOverride}/${imageName}.${isManifest ? 'png' : type}`;
}
if (pathPrefix && !isManifest) {
return `${pathPrefix}/${file_1.default.getRelativeImagePath(outputFilePath, imagePath)}`;
}
return file_1.default.getRelativeImagePath(outputFilePath, imagePath);
};
const generateIconsContentForManifest = (savedImages, options) => {
const purpose = `${options.maskable ? 'maskable ' : ''}any`;
return savedImages
.filter((image) => image.name.startsWith(constants_1.default.MANIFEST_ICON_FILENAME_PREFIX))
.map(({ path, width, height, name }) => ({
src: generateOutputPath(options, name, path, true),
sizes: `${width}x${height}`,
type: `image/${file_1.default.getExtension(path)}`,
purpose,
}));
};
const generateAppleTouchIconHtml = (savedImages, options) => {
return savedImages
.filter((image) => image.name.startsWith(constants_1.default.APPLE_ICON_FILENAME_PREFIX))
.map(({ path, name }) => constants_1.default.APPLE_TOUCH_ICON_META_HTML(generateOutputPath(options, name, path), options.xhtml))
.join('');
};
const generateFaviconHtml = (savedImages, options) => {
return savedImages
.filter((image) => image.name.startsWith(constants_1.default.FAVICON_FILENAME_PREFIX))
.map(({ width, path, name }) => constants_1.default.FAVICON_META_HTML(width, generateOutputPath(options, name, path), mime_types_1.lookup(path), options.xhtml))
.join('');
};
const generateMsTileImageHtml = (savedImages, options) => {
return savedImages
.filter((image) => image.name.startsWith(constants_1.default.MS_ICON_FILENAME_PREFIX))
.map(({ width, height, path, name }) => constants_1.default.MSTILE_IMAGE_META_HTML(constants_1.default.MSTILE_SIZE_ELEMENT_NAME_MAP[`${width}x${height}`], generateOutputPath(options, name, path), options.xhtml))
.join('');
};
const generateAppleLaunchImageHtml = (savedImages, options, darkMode) => {
return savedImages
.filter((image) => image.name.startsWith(constants_1.default.APPLE_SPLASH_FILENAME_PREFIX))
.map(({ width, height, path, name, scaleFactor, orientation }) => constants_1.default.APPLE_LAUNCH_SCREEN_META_HTML(width, height, generateOutputPath(options, name, path), scaleFactor, orientation, darkMode, options.xhtml))
.join('');
};
const generateHtmlForIndexPage = (savedImages, options) => {
const htmlMeta = {
[meta_1.HTMLMetaNames.appleMobileWebAppCapable]: `<meta name="apple-mobile-web-app-capable" content="yes"${options.xhtml ? ' /' : ''}>
`,
};
if (!options.splashOnly) {
if (options.favicon) {
htmlMeta[meta_1.HTMLMetaNames.favicon] = `${generateFaviconHtml(savedImages, options)}`;
}
htmlMeta[meta_1.HTMLMetaNames.appleTouchIcon] = `${generateAppleTouchIconHtml(savedImages, options)}`;
}
if (!options.iconOnly) {
if (options.darkMode) {
htmlMeta[meta_1.HTMLMetaNames.appleLaunchImageDarkMode] = `${generateAppleLaunchImageHtml(savedImages, options, true)}`;
}
else {
htmlMeta[meta_1.HTMLMetaNames.appleLaunchImage] = `${generateAppleLaunchImageHtml(savedImages, options, false)}`;
}
}
if (options.mstile) {
htmlMeta[meta_1.HTMLMetaNames.msTileImage] = `${generateMsTileImageHtml(savedImages, options)}`;
}
if (options.singleQuotes) {
Object.keys(htmlMeta).forEach((metaKey) => {
const metaContent = htmlMeta[metaKey];
if (metaContent) {
metaContent.replace(/"/gm, "'");
}
});
return htmlMeta;
}
return htmlMeta;
};
const addIconsToManifest = (manifestContent, manifestJsonFilePath) => __awaiter(void 0, void 0, void 0, function* () {
if (!(yield file_1.default.isPathAccessible(manifestJsonFilePath, file_1.default.WRITE_ACCESS))) {
throw Error(`Cannot write to manifest json file ${manifestJsonFilePath}`);
}
const manifestJson = JSON.parse((yield file_1.default.readFile(manifestJsonFilePath)));
const newManifestContent = Object.assign(Object.assign({}, manifestJson), { icons: [...manifestContent] });
if (manifestJson.icons) {
newManifestContent.icons = [
...newManifestContent.icons,
...manifestJson.icons.filter((icon) => !manifestContent.some((man) => man.sizes === icon.sizes)),
];
}
return file_1.default.writeFile(manifestJsonFilePath, JSON.stringify(newManifestContent, null, 2));
});
const formatMetaTags = (htmlMeta) => {
return constants_1.default.HTML_META_ORDERED_SELECTOR_LIST.reduce((acc, meta) => {
if (htmlMeta.hasOwnProperty(meta.name)) {
return `\
${acc}
${htmlMeta[meta.name]}`;
}
return acc;
}, '');
};
const addMetaTagsToIndexPage = (htmlMeta, indexHtmlFilePath, xhtml) => __awaiter(void 0, void 0, void 0, function* () {
if (!(yield file_1.default.isPathAccessible(indexHtmlFilePath, file_1.default.WRITE_ACCESS))) {
throw Error(`Cannot write to index html file ${indexHtmlFilePath}`);
}
const indexHtmlFile = yield file_1.default.readFile(indexHtmlFilePath);
const $ = cheerio_1.default.load(indexHtmlFile, {
decodeEntities: false,
xmlMode: xhtml,
});
const HEAD_SELECTOR = 'head';
const hasElement = (selector) => {
return $(selector).length > 0;
};
const hasDarkModeElement = () => {
const darkModeMeta = constants_1.default.HTML_META_ORDERED_SELECTOR_LIST.find((m) => m.name === meta_1.HTMLMetaNames.appleLaunchImageDarkMode);
if (darkModeMeta) {
return $(darkModeMeta.selector).length > 0;
}
return false;
};
// TODO: Find a way to remove tags without leaving newlines behind
constants_1.default.HTML_META_ORDERED_SELECTOR_LIST.forEach((meta) => {
if (htmlMeta.hasOwnProperty(meta.name) && htmlMeta[meta.name] !== '') {
const content = `${htmlMeta[meta.name]}`;
if (hasElement(meta.selector)) {
$(meta.selector).remove();
}
// Because meta tags with dark mode media attr has to be declared after the regular splash screen meta tags
if (meta.name === meta_1.HTMLMetaNames.appleLaunchImage &&
hasDarkModeElement()) {
$(HEAD_SELECTOR).prepend(`\n${content}`);
}
else {
$(HEAD_SELECTOR).append(`${content}\n`);
}
}
});
return file_1.default.writeFile(indexHtmlFilePath, $.html());
});
exports.default = {
formatMetaTags,
addIconsToManifest,
addMetaTagsToIndexPage,
generateHtmlForIndexPage,
generateBrowserConfigXml: generateMsTileImageHtml,
generateIconsContentForManifest,
};