/
ivanby
/
Compote
Обзор
Документация
Войти
/
ivanby
/
Compote
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
1
CI/CD
Аналитика
Безопасность
master
compote/src/compote.js
599 строк
24 KB
Иван Быков
Initial commit
19 июн 2026, 08:50
19 июн 2026, 08:50
b4391e5
Код
Авторство
О чём код?
/* jshint evil: true */ const toolName = 'compote -> '; //*** начало общий код для всех утилит const fs = require('node:fs'); const path = require('node:path'); const process = require('node:process'); const yaml = require('yaml'); let indicatorCurPos = 0; function indicator() { const indicatorChars = ['|', '/', '–', '\\']; if(indicatorCurPos < indicatorChars.length) indicatorCurPos++; if(indicatorCurPos >= indicatorChars.length) indicatorCurPos = 0; return indicatorChars[indicatorCurPos]; } const projectDir = process.cwd(); // доступен ли yaga.config.yaml const yagaConfigYamlPath = path.join( projectDir, 'yaga.config.yaml'); if (!fs.existsSync(yagaConfigYamlPath)) { console.log(toolName + '\x1b[31mПродолжение невозможно: отсутсвует yaga.config.yaml - ' + yagaConfigYamlPath + '\x1b[0m'); process.exit(1); } // читаем yaga.config.yaml let yagaConfigYaml = ''; try { yagaConfigYaml = fs.readFileSync( yagaConfigYamlPath , 'utf8'); } catch (err) { console.log(toolName + '\x1b[31mПродолжение невозможно: не могу прочитать yaga.config.yaml - ' + yagaConfigYamlPath + '\x1b[0m'); process.exit(1); } // парсим yaga.config.yaml let yagaConfig = ''; try { yagaConfig = yaml.parse(yagaConfigYaml); } catch (err) { console.log(toolName + '\x1b[31mПродолжение невозможно: не могу разобрать yaga.config.yaml - ' + yagaConfigYamlPath + '\x1b[0m'); console.log(err); process.exit(1); } // проверяем yagaConfig.target_dir if(!yagaConfig.target_dir || typeof yagaConfig.target_dir != 'string') { console.log(toolName + '\x1b[31mПродолжение невозможно: неверно указана целевая директория yagaConfig.target_dir - ' + yagaConfig.target_dir + '\x1b[0m'); process.exit(1); } const targetDirPath = path.join(projectDir, yagaConfig.target_dir); // проверяем yagaConfig.target_dir if(!fs.existsSync(targetDirPath)) { console.log(toolName + '\x1b[31mПродолжение невозможно: отсутсвует целевая директория yagaConfig.target_dir - ' + targetDirPath + '\x1b[0m'); process.exit(1); } //*** конец общий код для всех утилит // собственно код compote.js // проверяем yagaConfig.compote if( !yagaConfig.compote || !yagaConfig.compote.compote || typeof yagaConfig.compote.compote != 'string' || !/^[a-zA-Z$_][a-zA-Z0-9$_]*$/.test(yagaConfig.compote.compote) || !yagaConfig.compote.file || typeof yagaConfig.compote.file != 'string' || !yagaConfig.compote.filter || !Array.isArray(yagaConfig.compote.filter) || !yagaConfig.compote.ignore || !Array.isArray(yagaConfig.compote.ignore) ) { console.log(yagaConfig.compote); console.log(toolName + '\x1b[31mПродолжение невозможно: в ' + yagaConfigYamlPath + ' неккоректно настроен ключ compote\x1b[0m'); process.exit(1); } let srcFiles = []; function scanDir(dir) { fs.readdirSync(dir).forEach(file => { const absPath = path.join(dir, file); if (fs.statSync(absPath).isDirectory()) return scanDir(absPath); else { let cndf; for(let ref of yagaConfig.compote.filter) { try { cndf = (new RegExp(ref)).test(absPath); if(cndf) break; } catch (err) { console.log(toolName + '\x1b[31mПродолжение невозможно: в ' + yagaConfigYamlPath + ' неккоректно настроен ключ compote.filter\x1b[0m'); process.exit(1); } } if(cndf) { let cndi; for(let rei of yagaConfig.compote.ignore) { try { cndi = (new RegExp(rei)).test(absPath); if(cndi) break; } catch (err) { console.log(toolName + '\x1b[31mПродолжение невозможно: в ' + yagaConfigYamlPath + ' неккоректно настроен ключ compote.ignore\x1b[0m'); process.exit(1); } } if(!cndi) srcFiles.push(absPath); } } }); } scanDir(targetDirPath); //console.log(srcFiles); // npm install cheerio const cheerio = require('cheerio'); const HTMLM = require("html-minifier"); const htmlMinifier = { minify: (data)=>{ result = HTMLM.minify(data,yagaConfig.compote['minify_html']); return result; } }; const CCSS = new (require('clean-css'))({ inline: ['none'] // disables all inlining }); const cleanCSS = { minify: (data)=>{ if(yagaConfig.compote['minify_style']) result = CCSS.minify(data); else return data; if(result.errors && result.errors.length) throw result.errors; else return result.styles; } }; let $; let compote_path; if( yagaConfig.compote.mount && yagaConfig.compote.mount.element && yagaConfig.compote.root && yagaConfig.compote.name ) { compote_path = `let _prts = this.parents(_co).map(i=>i.compote.forename); _prts[0] = '${yagaConfig.compote.name}'; _co.compote.id = _prts.join('.').replace(/\\.(\\d+)/g, '[$1]');`; } else compote_path = `_co.compote.id = this.parents(_co).map(i=>i.compote.forename).join('.').replace(/\\.(\\d+)/g, '[$1]');`; //compote_path = `_co.getElem().setAttribute('compote-id', _co.compote.id);`; if(typeof yagaConfig.compote.marked == 'string') yagaConfig.compote.marked = yagaConfig.compote.marked.trim(); else yagaConfig.compote.marked = ''; let out_script = `/**@insert out_script.js --grave */`; let compoteNoti = toolName + 'Генерирую код в ./' + yagaConfig.target_dir + '/' + yagaConfig.compote.file + ': '; process.stdout.write(compoteNoti); const compotes = {}; let props_attrs = ''; props_attrs_idx = 0; let setlist_flag = false; const handFiles = ()=>{ srcFileOfSrcFiles: for( let srcFile of srcFiles) { let srcText = ''; try { srcText = fs.readFileSync( srcFile , 'utf8'); } catch (err) { console.log(); console.log(toolName + '\x1b[31mПродолжение невозможно: не могу прочитать ' + srcFile + '\x1b[0m'); console.log(err); process.exit(1); } $ = cheerio.load(srcText, { xml: { xmlMode: false, emptyAttrs: false, decodeEntities: false, lowerCaseAttributeNames: true, lowerCaseTags: true } }); let comp_lnk = $('[compote]'); if(!comp_lnk || comp_lnk.length === 0) // это не файл компонента continue; if(comp_lnk.length !== 1) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: допустим только один компонент на файл:\n ' + srcFile + '\x1b[0m'); console.log(); continue; } let comp_name = comp_lnk.attr('compote'); if(!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(comp_name)) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: неверное имя компонента (должно быть /^[a-zA-Z_$][a-zA-Z0-9_$]*$/):\n ' + comp_name + ' в ' + srcFile + '\x1b[0m'); console.log(); continue; } if(Object.keys(compotes).includes(comp_name)) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: дубликат компонента:\n ' + comp_name + ' в ' + srcFile + '\x1b[0m'); console.log(); continue; } let comp_nesteds = comp_lnk.children('[nested]'); let setlist = comp_lnk.attr('setlist'); if(comp_nesteds && comp_nesteds.length) { for(let cmp_nested of comp_nesteds) { let nested_name, nested_nm, nested_vl; let $cmp_nested = $(cmp_nested); // способ №1 -- имя вкладываемого в значении, включая возможный as nested_name = $cmp_nested.attr('nested'); if(nested_name) { nested_name = nested_name.trim(); let exp_as = nested_name.split(/\s+as\s+/); if(!/^[a-zA-Z$_][a-zA-Z0-9$_]*$/.test(exp_as[0])) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: вложенный компонент имеет некорректное имя (в значении "nested"):\n ' + exp_as[0] + ' в ' + srcFile + '\x1b[0m'); console.log(); continue srcFileOfSrcFiles; } if(exp_as[1] && !/^[a-zA-Z$_][a-zA-Z0-9$_]*$/.test(exp_as[1])) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: вложенный компонент имеет некорректное назначаемое имя (в значении "nested" после "as"):\n ' + exp_as[1] + ' в ' + srcFile + '\x1b[0m'); console.log(); continue srcFileOfSrcFiles; } nested_name = exp_as[0]; if(exp_as[1]) nested_name += ' as ' + exp_as[1]; $cmp_nested.attr('nested', nested_name); nested_nm = exp_as[0]; nested_vl = exp_as[1]; } // способ №2 -- имя вкладываемого как отдельный атрибут else { nested_name = Object.keys(cmp_nested.attribs).find(nm=>nm != 'nested' && nm != 'props'); if(!nested_name || !/^[a-z_][a-z0-9_]*$/.test(nested_name)) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: вложенный компонент имеет некорректное имя (как атрибут):\n ' + nested_name + ' в ' + srcFile + '\x1b[0m'); console.log(); continue srcFileOfSrcFiles; } let nested_as = $cmp_nested.attr(nested_name); if(nested_as) nested_as = nested_as.trim(); if(nested_as && !/^[a-zA-Z$_][a-zA-Z0-9$_]*$/.test(nested_as)) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: вложенный компонент имеет некорректное назначаемое имя (как значение атрибута):\n ' + nested_as + ' в ' + srcFile + '\x1b[0m'); console.log(); continue srcFileOfSrcFiles; } $cmp_nested.attr(nested_name, nested_as); nested_nm = nested_name; nested_vl = nested_as; } if(nested_nm === comp_name) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: вложенный компонент не может быть одновременно объявляемым:\n ' + nested_nm + ' в ' + srcFile + '\x1b[0m'); console.log(); continue srcFileOfSrcFiles; } if(nested_vl === comp_name) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: вложенный компонент не может быть одновременно объявляемым:\n ' + nested_vl + ' в ' + srcFile + '\x1b[0m'); console.log(); continue srcFileOfSrcFiles; } let cmp_props = cmp_nested.attribs['props']; if(typeof setlist != 'string' && cmp_props && typeof cmp_props == 'string') { cmp_props = cmp_props.trim(); try { if(cmp_props[0] !== '{' || cmp_props[cmp_props.length-1] !== '}') throw 0; let test_obj = (new Function('return '+cmp_props+';'))(); if(!test_obj || typeof test_obj != 'object') throw 0; cmp_nested.attribs['props'] = props_attrs_idx+''; props_attrs_idx++; props_attrs += `\n${cmp_props},`; } catch(err) { console.log(cmp_nested); console.log(toolName + '\x1b[31mИгнорирую: props во вложенном компоненте, если указан, должен быть валидным объектом:\n ' + comp_name + ' в ' + srcFile + '\x1b[0m'); console.log(); continue srcFileOfSrcFiles; } } } } compotes[comp_name] = {}; if(typeof setlist == 'string') { // это компонент-список setlist! if(!setlist.trim()) setlist = 'setlist'; if(!/^[a-zA-Z$_][a-zA-Z0-9$_]*$/.test(setlist)) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: неверное имя setlist (должно быть /^[a-zA-Z$_][a-zA-Z0-9$_]*$/):\n ' + comp_name + ' в ' + srcFile + '\x1b[0m'); console.log(); continue; } let tpl_comp = comp_lnk.children('[nested]'); if(!tpl_comp || tpl_comp.length !== 1) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: компонент-список "setlist" должен иметь один и только один вложенный компонент-шаблон:\n ' + comp_name + ' в ' + srcFile + '\x1b[0m'); console.log(); continue; } tpl_comp = tpl_comp[0]; $tpl_comp = $(tpl_comp); // способ №1 -- имя вкладываемого в значении template_name = $tpl_comp.attr('nested'); if(template_name) { template_name = template_name.trim(); if(!/^[a-zA-Z$_][a-zA-Z0-9$_]*$/.test(template_name)) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: компонент-шаблон имеет некорректное имя (в значении "nested"):\n ' + template_name + ' в ' + srcFile + '\x1b[0m'); console.log(); continue srcFileOfSrcFiles; } $tpl_comp.attr('nested', template_name); } // способ №2 -- имя вкладываемого как отдельный атрибут else { template_name = Object.keys(tpl_comp.attribs).find(nm=>nm != 'nested' && nm != 'props'); if(!template_name || !/^[a-z_][a-z0-9_]*$/.test(template_name)) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: компонент-шаблон имеет некорректное имя (как атрибут):\n ' + template_name + ' в ' + srcFile + '\x1b[0m'); console.log(); continue srcFileOfSrcFiles; } } if(template_name === comp_name) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: компонент-шаблон не может быть одновременно объявляемым списком:\n ' + tpl_nm + ' в ' + srcFile + '\x1b[0m'); console.log(); continue srcFileOfSrcFiles; } let template_props = tpl_comp.attribs['props']; if(template_props && typeof template_props == 'string') { template_props = template_props.trim(); try { if(template_props[0] !== '{' || template_props[template_props.length-1] !== '}') throw 0; let test_obj = (new Function('return '+template_props+';'))(); if(!test_obj || typeof test_obj != 'object') throw 0; } catch(err) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: props в компоненте-шаблоне, если указан, должен быть валидным объектом:\n ' + comp_name + ' в ' + srcFile + '\x1b[0m'); console.log(); continue; } } else template_props = '{}'; compotes[comp_name].setlist = { template_name: template_name, // имя шаблона-компонента template_props: template_props // инициирущий пропс шаблона-компонента }; comp_lnk.html(''); // чистим компонент-список } compotes[comp_name].html = htmlMinifier.minify(comp_lnk.prop('outerHTML').replaceAll('`','\\`')); let scrpt_lnk = $('script'); if(scrpt_lnk && scrpt_lnk.length > 1) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: в компоненте больше одного блока script:\n ' + comp_name + ' в ' + srcFile + '\x1b[0m'); console.log(); continue; } //compotes[comp_name].script = scrpt_lnk.length? scrpt_lnk.html().replaceAll('`','\\`') : ''; compotes[comp_name].script = scrpt_lnk.length? scrpt_lnk.html() : ''; let stl_lnk = $('style'); if(stl_lnk && stl_lnk.length > 1) { console.log(); console.log(toolName + '\x1b[31mИгнорирую: в компоненте больше одного блока style:\n ' + comp_name + ' в ' + srcFile + '\x1b[0m'); console.log(); continue; } compotes[comp_name].style = stl_lnk.length? cleanCSS.minify(stl_lnk.html()).replaceAll('`','\\`') : ''; let style_code = ''; if(compotes[comp_name].style.length) style_code =` var _dqshd_ = document.querySelector('html>head'); if(!_dqshd_.querySelector('style[compote-style=${comp_name}]')) _dqshd_.appendChild(${comp_name}.newElem(\`<style compote-style="${comp_name}">${compotes[comp_name].style}</style>\`)); _dqshd_ = undefined; `; let props_code = ''; if(compotes[comp_name].setlist) { if(!setlist_flag) { out_script += `/**@insert out_script-setlist.js --grave */`; setlist_flag = true; } props_code += `/**@insert props_code-setlist.js --grave */`; } else { props_code += ` const $izba_props = $this.props; $this.props = prps => this.props_($this, prps, $izba_props); `; } if( yagaConfig.compote.mount && yagaConfig.compote.mount.element === 'body' && yagaConfig.compote.mount.method === 'replaceWith' && yagaConfig.compote.root === comp_name ) out_script += // body и replaceWith const $index = $forename; `${comp_name}: function ($props, $parent, $forename) { const ${comp_name} = $izba('body'${yagaConfig.compote.izba? ','+yagaConfig.compote.izba : ''}); const $this = ${comp_name}; ${style_code} ${props_code} $this.compote = {}; this.nested_($this, $props, $parent, $forename); ${compotes[comp_name].script.includes('$elem')? 'const $elem = $this.getElem();' : ''} ${compotes[comp_name].script.includes('$root')? 'const $root = $this.root;' : ''} ${compotes[comp_name].script} return $this; }, `; else out_script += `${comp_name}: function ($props, $parent, $forename) { const ${comp_name} = $izba(\`${compotes[comp_name].html}\`${yagaConfig.compote.izba? ','+yagaConfig.compote.izba : ''}); const $this = ${comp_name}; ${style_code} ${props_code} $this.compote = {}; this.nested_($this, $props, $parent, $forename); ${compotes[comp_name].script.includes('$elem')? 'const $elem = $this.getElem();' : ''} ${compotes[comp_name].script.includes('$root')? 'const $root = $this.root;' : ''} ${compotes[comp_name].script.includes('$index')? 'const $index = $forename;' : ''} ${compotes[comp_name].script} return $this; }, `; try { fs.unlinkSync(srcFile); } catch (err) { console.log(toolName + '\x1b[31mПродолжение невозможно: не могу удалить ' + srcFile + '\x1b[0m'); console.log(err); process.exit(1); } } out_script += `props_attrs_: [${props_attrs} ], `; if( yagaConfig.compote.mount && typeof yagaConfig.compote.mount.element == 'string' && yagaConfig.compote.root && yagaConfig.compote.name ) { let on_ready = typeof yagaConfig.compote.mount.on_ready == 'string'; out_script += `mount: function () {`; if(on_ready) out_script += `document.addEventListener("${yagaConfig.compote.mount.on_ready}", () => {`; if( !(yagaConfig.compote.mount.element === 'body' && yagaConfig.compote.mount.method === 'replaceWith') ) out_script += ` window.${yagaConfig.compote.name} = window.${yagaConfig.compote.compote}.${yagaConfig.compote.root}(); document.querySelector('${yagaConfig.compote.mount.element}') .${['prepend', 'append', 'replaceWith'].includes(yagaConfig.compote.mount.method)? yagaConfig.compote.mount.method : 'append'}(window.${yagaConfig.compote.name}.getElem()); window.${yagaConfig.compote.compote}.root = window.${yagaConfig.compote.name};`; else // body и replaceWith out_script += ` const srcEl = $izba.newElem(\`${compotes[yagaConfig.compote.root].html}\`); const dstEl = document.querySelector('body'); [...srcEl.attributes].forEach( attr => dstEl.setAttribute(attr.nodeName, attr.nodeValue) ); while (srcEl.firstChild) dstEl.appendChild(srcEl.firstChild); window.${yagaConfig.compote.name} = window.${yagaConfig.compote.compote}.${yagaConfig.compote.root}(); window.${yagaConfig.compote.compote}.root = window.${yagaConfig.compote.name};`; if(on_ready) out_script += `});`; out_script += ` }};`; if(yagaConfig.compote.mount.auto) out_script += ` window.${yagaConfig.compote.compote}.mount();`; } else out_script += ` }};`; }; handFiles(); //console.log(out_script); try { fs.writeFileSync( targetDirPath + '/' + yagaConfig.compote.file , out_script, 'utf8'); } catch (err) { console.log(); console.log(toolName + '\x1b[31mПродолжение невозможно: не могу записать ' + srcFile + '\x1b[0m'); console.log(err); process.exit(1); } process.stdout.write('\b'); process.stdout.write('\x1b[32mготово\x1b[0m'); console.log(); /* // два промиса // после подключения компонента в DOM // если назначен этот промис afterConnect // до удаления из DOM если назначен этот промис beforeRemove */