import path from "path"; import { WORKING_DIR } from "../constants"; import fs from 'fs/promises' import JSZip from "jszip"; import crypto from "crypto"; import { Logger } from "../logger"; const logger = Logger('compiler'); export interface BundleFile { file: string; type: string; main: boolean; } const binaryFileTypes = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'mp4', 'mp3', 'wav', 'ogg', 'pdf', 'zip', 'rar', '7z']; export async function compileBundleFile(zip : JSZip) { let timestart = Date.now(); logger.log("Starting compilation of bundle file"); let files : BundleFile[] = await getAllFilesInBundleForCompile(zip); if(files.length == 0){ logger.log("No files to compile in bundle"); throw new Error("No files to compile in bundle"); } let compileOutputDir = path.join(WORKING_DIR, 'b'); await fs.mkdir(compileOutputDir, { recursive: true }); let mainFiles = files.filter(f => f.main); let otherFiles = files.filter(f => !f.main); logger.log(`Compiling ${files.length} files, ${mainFiles.length} main files and ${otherFiles.length} other files`); for (let file of files) { let zipFile = zip.file(file.file); if (zipFile) { let content = await zipFile.async('nodebuffer'); let outputPath = path.join(compileOutputDir, await hashFileName(path.basename(file.file))); await fs.writeFile(outputPath, content); } } await compileHtmlFile(mainFiles); await transitionFilenamesInFiles([...mainFiles, ...otherFiles]); logger.log("Compilation done successfully in " + (Date.now() - timestart) + "ms"); } async function transitionFilenamesInFiles(otherFiles: BundleFile[]) { logger.log(`Transitioning filenames in ${otherFiles.length} files`); for(let i = 0; i < otherFiles.length; i++){ let file = otherFiles[i]; let filePath = path.join(WORKING_DIR, 'b', await hashFileName(path.basename(file.file))); let buffer = await fs.readFile(filePath, { encoding: file.type && binaryFileTypes.includes(file.type) ? 'binary' : 'utf-8' }); let content = buffer.toString(); logger.log(`Processing file ${path.basename(file.file)}`); for(let j = 0; j < otherFiles.length; j++){ let targetFile = otherFiles[j]; let originalName = path.basename(targetFile.file); let hashedName = await hashFileName(originalName); let entries = content.split(originalName); if(entries.length <= 0){ continue; } logger.log(`In file ${path.basename(file.file)} replacing ${originalName} with ${hashedName} (${entries.length - 1} occurrences)`); content = entries.join(hashedName); } logger.log(`Flush transitioned file ${path.basename(file.file)}`); await fs.writeFile(filePath, content, { encoding: file.type && binaryFileTypes.includes(file.type) ? 'binary' : 'utf-8' }); } } async function compileHtmlFile(mainFiles: BundleFile[]) { let html = await constructHtmlTemplateToCompile(); html = html.replace(``, ''); let scripts = mainFiles.filter(f => f.type === 'js').map(f => f.file); let styles = mainFiles.filter(f => f.type === 'css').map(f => f.file); let scriptTags : string = ""; for (let script of scripts) { let hashedName = await hashFileName(path.basename(script)); scriptTags += `\n`; } let styleTags : string = ""; for (let style of styles) { let hashedName = await hashFileName(path.basename(style)); styleTags += `\n`; } html = html.replace('', `${scriptTags}\n${styleTags}`); html = html.replace('\n', ''); let outputHtmlPath = path.join(WORKING_DIR, 'b', 'j.html'); await fs.writeFile(outputHtmlPath, html); } export async function getAllFilesInBundleForCompile(zip : JSZip) : Promise { const mainFilePrefixes = ['index-', 'main-']; return Object.keys(zip.files) .filter(filePath => filePath.indexOf('__MACOSX') == -1) .map(filePath => { const ext = path.extname(filePath).slice(1); const isMain = mainFilePrefixes.some(prefix => path.basename(filePath).startsWith(prefix)); return { file: filePath, type: ext, main: isMain } as BundleFile; }); } async function constructHtmlTemplateToCompile() { let buffer = await fs.readFile( path.join(__dirname, '../../resources/prod.html') ); let html = buffer.toString(); return html; } async function hashFileName(name : string) : Promise { return crypto.createHash('md5').update(name).digest('hex') + path.extname(name); }