Files
desktop/lib/main/boot/compiler.ts
rosetta 83f38dc63f 'init'
2026-01-30 05:01:05 +02:00

130 lines
4.9 KiB
TypeScript

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(`<script type="module" src="/renderer.tsx"></script>`, '');
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 += `<script type="module" src="${hashedName}"></script>\n`;
}
let styleTags : string = "";
for (let style of styles) {
let hashedName = await hashFileName(path.basename(style));
styleTags += `<link rel="stylesheet" href="${hashedName}">\n`;
}
html = html.replace('<!--RR-->', `${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<BundleFile[]> {
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<string> {
return crypto.createHash('md5').update(name).digest('hex') + path.extname(name);
}