const { resolve, relative, sep } = require('path') const { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream, } = require('fs') var zlib = require('zlib') var mime = require('mime-types') const ARDUINO_INCLUDES = '#include \n\n' function getFilesSync(dir, files = []) { readdirSync(dir, { withFileTypes: true }).forEach((entry) => { const entryPath = resolve(dir, entry.name) if (entry.isDirectory()) { getFilesSync(entryPath, files) } else { files.push(entryPath) } }) return files } function coherseToBuffer(input) { return Buffer.isBuffer(input) ? input : Buffer.from(input) } function cleanAndOpen(path) { if (existsSync(path)) { unlinkSync(path) } return createWriteStream(path, { flags: 'w+' }) } class ProgmemGenerator { constructor(options = {}) { const { outputPath, bytesPerLine = 20, indent = ' ', includes = ARDUINO_INCLUDES, } = options this.options = { outputPath, bytesPerLine, indent, includes } } apply(compiler) { compiler.hooks.emit.tapAsync( { name: 'ProgmemGenerator' }, (compilation, callback) => { const { outputPath, bytesPerLine, indent, includes } = this.options const fileInfo = [] const writeStream = cleanAndOpen( resolve(compilation.options.context, outputPath), ) try { const writeIncludes = () => { writeStream.write(includes) } const writeFile = (relativeFilePath, buffer) => { const variable = 'ESP_REACT_DATA_' + fileInfo.length const mimeType = mime.lookup(relativeFilePath) var size = 0 writeStream.write('const uint8_t ' + variable + '[] PROGMEM = {') const zipBuffer = zlib.gzipSync(buffer) zipBuffer.forEach((b) => { if (!(size % bytesPerLine)) { writeStream.write('\n') writeStream.write(indent) } writeStream.write( '0x' + ('00' + b.toString(16).toUpperCase()).substr(-2) + ',', ) size++ }) if (size % bytesPerLine) { writeStream.write('\n') } writeStream.write('};\n\n') fileInfo.push({ uri: '/' + relativeFilePath.replace(sep, '/'), mimeType, variable, size, }) } const writeFiles = () => { // process static files const buildPath = compilation.options.output.path for (const filePath of getFilesSync(buildPath)) { const readStream = readFileSync(filePath) const relativeFilePath = relative(buildPath, filePath) writeFile(relativeFilePath, readStream) } // process assets const { assets } = compilation Object.keys(assets).forEach((relativeFilePath) => { writeFile( relativeFilePath, coherseToBuffer(assets[relativeFilePath].source()), ) }) } const generateWWWClass = () => { return `typedef std::function RouteRegistrationHandler; class WWWData { ${indent}public: ${indent.repeat( 2, )}static void registerRoutes(RouteRegistrationHandler handler) { ${fileInfo .map( (file) => `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${ file.variable }, ${file.size});`, ) .join('\n')} ${indent.repeat(2)}} }; ` } const writeWWWClass = () => { writeStream.write(generateWWWClass()) } writeIncludes() writeFiles() writeWWWClass() writeStream.on('finish', () => { callback() }) } finally { writeStream.end() } }, ) } } module.exports = ProgmemGenerator