/** * Writes compressed C arrays of data files (web interface) * How to use it? * * 1) Install Node 11+ and npm * 2) npm install * 3) npm run build * * If you change data folder often, you can run it in monitoring mode (it will recompile and update *.h on every file change) * * > npm run dev * * How it works? * * It uses NodeJS packages to inline, minify and GZIP files. See writeHtmlGzipped and writeChunks invocations at the bottom of the page. */ const fs = require("fs"); /** * */ function hexdump(buffer) { let lines = []; for (let i = 0; i < buffer.length; i += 16) { let block = buffer.slice(i, i + 16); // cut buffer into blocks of 16 let hexArray = []; for (let value of block) { hexArray.push("0x" + value.toString(16).padStart(2, "0")); } let hexString = hexArray.join(", "); let line = ` ${hexString}`; lines.push(line); } return lines.join(",\n"); } const inliner = require("inliner"); const zlib = require("zlib"); function writeHtmlGzipped(sourceFile, resultFile) { console.info("Reading " + sourceFile); new inliner(sourceFile, function(error, html) { console.info("Inlined " + html.length + " characters"); if (error) { console.warn(error); throw error; } zlib.gzip(html, function(error, result) { if (error) { console.warn(error); throw error; } console.info("Compressed " + result.length + " bytes"); const array = hexdump(result); const src = ` // Autogenerated from ${sourceFile}, do not edit!! const uint16_t PAGE_index_L = ${result.length}; const uint8_t PAGE_index[] PROGMEM = { ${array} }; `; console.info("Writing " + resultFile); fs.writeFileSync(resultFile, src); }); }); } const CleanCSS = require("clean-css"); const MinifyHTML = require("html-minifier").minify; function filter(str, type) { if (type === undefined) { return str; } else if (type == "css-minify") { return new CleanCSS({}).minify(str).styles; } else if (type == "html-minify") { return MinifyHTML(str, { collapseWhitespace: true, maxLineLength: 80, minifyCSS: true, minifyJS: true, continueOnParseError: false }); } else { console.warn("Unknown filter: " + type); return str; } } function specToChunk(srcDir, s) { if (s.method == "plaintext") { const buf = fs.readFileSync(srcDir + "/" + s.file); const str = buf.toString("ascii"); const chunk = ` // Autogenerated from ${srcDir}/${s.file}, do not edit!! const char ${s.name}[] PROGMEM = R"${s.prepend || ""}${filter( str, s.filter )}${s.append || ""}"; `; return s.mangle ? s.mangle(chunk) : chunk; } else if (s.method == "binary") { const buf = fs.readFileSync(srcDir + "/" + s.file); const result = hexdump(buf); const chunk = ` // Autogenerated from ${srcDir}/${s.file}, do not edit!! const uint16_t ${s.name}_length = ${result.length}; const uint8_t ${s.name}[] PROGMEM = { ${result} }; `; return s.mangle ? s.mangle(chunk) : chunk; } else { console.warn("Unknown method: " + s.method); return undefined; } } function writeChunks(srcDir, specs, resultFile) { let src = ""; specs.forEach(s => { try { console.info("Reading " + srcDir + "/" + s.file + " as " + s.name); src += specToChunk(srcDir, s); } catch (e) { console.warn( "Failed " + s.name + " from " + srcDir + "/" + s.file, e.message.length > 60 ? e.message.substring(0, 60) : e.message ); } }); console.info("Writing " + src.length + " characters into " + resultFile); fs.writeFileSync(resultFile, src); } writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h"); writeChunks( "wled00/data", [ { file: "style.css", name: "PAGE_settingsCss", prepend: "=====()=====", method: "plaintext", filter: "css-minify" }, { file: "settings.htm", name: "PAGE_settings", prepend: "=====(", append: ")=====", method: "plaintext", filter: "html-minify" }, { file: "settings_wifi.htm", name: "PAGE_settings_wifi", prepend: "=====(", append: ")=====", method: "plaintext", filter: "html-minify", mangle: str => str .replace(/\/gms, "") .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") }, { file: "settings_leds.htm", name: "PAGE_settings_leds", prepend: "=====(", append: ")=====", method: "plaintext", filter: "html-minify", mangle: str => str .replace(/\/gms, "") .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") }, { file: "settings_dmx.htm", name: "PAGE_settings_dmx", prepend: "=====(", append: ")=====", method: "plaintext", filter: "html-minify", mangle: str => { const nocss = str .replace(/\/gms, "") .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%"); return ` #ifdef WLED_ENABLE_DMX ${nocss} #else const char PAGE_settings_dmx[] PROGMEM = R"=====()====="; #endif `; } }, { file: "settings_ui.htm", name: "PAGE_settings_ui", prepend: "=====(", append: ")=====", method: "plaintext", filter: "html-minify", mangle: str => str .replace(/\/gms, "") .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") }, { file: "settings_sync.htm", name: "PAGE_settings_sync", prepend: "=====(", append: ")=====", method: "plaintext", filter: "html-minify", mangle: str => str .replace(/\/gms, "") .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") }, { file: "settings_time.htm", name: "PAGE_settings_time", prepend: "=====(", append: ")=====", method: "plaintext", filter: "html-minify", mangle: str => str .replace(/\/gms, "") .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") }, { file: "settings_sec.htm", name: "PAGE_settings_sec", prepend: "=====(", append: ")=====", method: "plaintext", filter: "html-minify", mangle: str => str .replace(/\/gms, "") .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") } ], "wled00/html_settings.h" ); writeChunks( "wled00/data", [ { file: "usermod.htm", name: "PAGE_usermod", prepend: "=====(", append: ")=====", method: "plaintext", filter: "html-minify" }, { file: "msg.htm", name: "PAGE_msg", prepend: "=====(", append: ")=====", method: "plaintext", filter: "html-minify" }, { file: "dmxmap.htm", name: "PAGE_dmxmap", prepend: "=====(", append: ")=====", method: "plaintext", filter: "html-minify", mangle: str => ` #ifdef WLED_ENABLE_DMX ${str} #else const char PAGE_dmxmap[] PROGMEM = R"=====()====="; #endif ` }, { file: "update.htm", name: "PAGE_update", prepend: "=====(", append: ")=====", method: "plaintext", filter: "html-minify" }, { file: "welcome.htm", name: "PAGE_welcome", prepend: "=====(", append: ")=====", method: "plaintext", filter: "html-minify" }, { file: "liveview.htm", name: "PAGE_liveview", prepend: "=====(", append: ")=====", method: "plaintext", filter: "html-minify" }, { file: "favicon.ico", name: "favicon", method: "binary" } ], "wled00/html_other.h" );