UNPKG

muspe-cli

Version:

MusPE Advanced Framework v2.1.3 - Mobile User-friendly Simple Progressive Engine with Enhanced CLI Tools, Specialized E-Commerce Templates, Material Design 3, Progressive Enhancement, Mobile Optimizations, Performance Analysis, and Enterprise-Grade Develo

449 lines (381 loc) 12.1 kB
// MusPE Template Compiler - Compile templates with data binding and directives class MusPETemplateCompiler { constructor() { this.directives = new Map(); this.components = new Map(); this.filters = new Map(); // Register built-in directives this.registerBuiltinDirectives(); this.registerBuiltinFilters(); } // Compile template string to render function compile(template, options = {}) { const ast = this.parse(template); const code = this.generate(ast, options); // Create render function return new Function('h', 'state', 'props', 'methods', 'computed', 'refs', code); } // Parse template to AST parse(template) { const tokens = this.tokenize(template); return this.parseTokens(tokens); } // Tokenize template tokenize(template) { const tokens = []; let current = 0; while (current < template.length) { let char = template[current]; // Handle mustache syntax {{ }} if (char === '{' && template[current + 1] === '{') { let value = ''; current += 2; // Skip {{ while (current < template.length && !(template[current] === '}' && template[current + 1] === '}')) { value += template[current]; current++; } current += 2; // Skip }} tokens.push({ type: 'expression', value: value.trim() }); continue; } // Handle directives m- if (char === 'm' && template[current + 1] === '-') { let directive = ''; let directiveValue = ''; // Find directive name current += 2; while (current < template.length && template[current] !== '=' && template[current] !== ' ' && template[current] !== '>') { directive += template[current]; current++; } // Find directive value if (template[current] === '=') { current++; // Skip = if (template[current] === '"' || template[current] === "'") { const quote = template[current]; current++; // Skip opening quote while (current < template.length && template[current] !== quote) { directiveValue += template[current]; current++; } current++; // Skip closing quote } } tokens.push({ type: 'directive', name: directive, value: directiveValue }); continue; } // Handle regular text let text = ''; while (current < template.length && !(template[current] === '{' && template[current + 1] === '{') && !(template[current] === 'm' && template[current + 1] === '-')) { text += template[current]; current++; } if (text) { tokens.push({ type: 'text', value: text }); } } return tokens; } // Parse tokens to AST parseTokens(tokens) { const ast = { type: 'root', children: [] }; let current = 0; while (current < tokens.length) { const token = tokens[current]; if (token.type === 'text') { // Parse HTML elements from text const elements = this.parseHTML(token.value); ast.children.push(...elements); } else if (token.type === 'expression') { ast.children.push({ type: 'expression', expression: token.value }); } else if (token.type === 'directive') { ast.children.push({ type: 'directive', name: token.name, value: token.value }); } current++; } return ast; } // Parse HTML elements parseHTML(html) { const parser = new DOMParser(); const doc = parser.parseFromString(`<div>${html}</div>`, 'text/html'); const container = doc.body.firstChild; return this.parseNode(container).children; } // Parse DOM node to AST parseNode(node) { if (node.nodeType === Node.TEXT_NODE) { return { type: 'text', value: node.textContent }; } if (node.nodeType === Node.ELEMENT_NODE) { const element = { type: 'element', tag: node.tagName.toLowerCase(), attributes: {}, children: [] }; // Parse attributes for (const attr of node.attributes) { if (attr.name.startsWith('m-')) { // Directive element.directives = element.directives || []; element.directives.push({ name: attr.name.slice(2), value: attr.value }); } else if (attr.name.startsWith(':')) { // Bind attribute element.attributes[attr.name.slice(1)] = { type: 'expression', value: attr.value }; } else if (attr.name.startsWith('@')) { // Event listener element.attributes[`on${attr.name.slice(1)}`] = { type: 'event', value: attr.value }; } else { // Regular attribute element.attributes[attr.name] = attr.value; } } // Parse children for (const child of node.childNodes) { const childAst = this.parseNode(child); if (childAst) { element.children.push(childAst); } } return element; } return null; } // Generate render code from AST generate(ast, options = {}) { return `return ${this.generateNode(ast)};`; } // Generate code for single node generateNode(node) { if (node.type === 'root') { if (node.children.length === 1) { return this.generateNode(node.children[0]); } else { const children = node.children.map(child => this.generateNode(child)).join(', '); return `h('fragment', {}, ${children})`; } } if (node.type === 'text') { return `h('text', { content: ${JSON.stringify(node.value)} })`; } if (node.type === 'expression') { return `h('text', { content: String(${node.expression}) })`; } if (node.type === 'element') { const tag = JSON.stringify(node.tag); const props = this.generateProps(node.attributes, node.directives); const children = node.children.map(child => this.generateNode(child)).join(', '); return `h(${tag}, ${props}, ${children})`; } return 'null'; } // Generate props object generateProps(attributes, directives = []) { const props = []; // Regular attributes for (const [name, value] of Object.entries(attributes)) { if (typeof value === 'object' && value.type === 'expression') { props.push(`${JSON.stringify(name)}: ${value.value}`); } else if (typeof value === 'object' && value.type === 'event') { props.push(`${JSON.stringify(name)}: ${value.value}`); } else { props.push(`${JSON.stringify(name)}: ${JSON.stringify(value)}`); } } // Directives for (const directive of directives) { const directiveHandler = this.directives.get(directive.name); if (directiveHandler) { const directiveProps = directiveHandler.generate(directive.value); props.push(...directiveProps); } } return `{ ${props.join(', ')} }`; } // Register directive registerDirective(name, directive) { this.directives.set(name, directive); } // Register built-in directives registerBuiltinDirectives() { // m-if directive this.registerDirective('if', { generate: (value) => [`style: (${value}) ? {} : { display: 'none' }`] }); // m-show directive this.registerDirective('show', { generate: (value) => [`style: (${value}) ? {} : { display: 'none' }`] }); // m-for directive this.registerDirective('for', { generate: (value) => { // Parse "item in items" syntax const match = value.match(/(\w+)\s+in\s+(.+)/); if (match) { const [, item, items] = match; return [`'data-for': ${JSON.stringify(value)}`]; } return []; } }); // m-model directive (two-way binding) this.registerDirective('model', { generate: (value) => [ `value: ${value}`, `onInput: (e) => { ${value} = e.target.value }` ] }); // m-on directive (event listeners) this.registerDirective('on', { generate: (value) => { const [event, handler] = value.split(':'); return [`on${event.charAt(0).toUpperCase() + event.slice(1)}: ${handler}`]; } }); // m-bind directive (dynamic attributes) this.registerDirective('bind', { generate: (value) => { const [attr, expression] = value.split(':'); return [`${attr}: ${expression}`]; } }); // m-class directive (dynamic classes) this.registerDirective('class', { generate: (value) => [`class: ${value}`] }); // m-style directive (dynamic styles) this.registerDirective('style', { generate: (value) => [`style: ${value}`] }); } // Register filter registerFilter(name, filter) { this.filters.set(name, filter); } // Register built-in filters registerBuiltinFilters() { this.registerFilter('uppercase', (value) => String(value).toUpperCase()); this.registerFilter('lowercase', (value) => String(value).toLowerCase()); this.registerFilter('capitalize', (value) => { const str = String(value); return str.charAt(0).toUpperCase() + str.slice(1); }); this.registerFilter('currency', (value, currency = 'USD') => { return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(value); }); this.registerFilter('date', (value, format = 'en-US') => { return new Date(value).toLocaleDateString(format); }); this.registerFilter('json', (value) => JSON.stringify(value, null, 2)); } // Apply filters to value applyFilters(value, filterString) { const filters = filterString.split('|').map(f => f.trim()); let result = value; for (const filterName of filters) { const filter = this.filters.get(filterName); if (filter) { result = filter(result); } } return result; } // Precompile templates for better performance precompile(templates) { const compiled = {}; for (const [name, template] of Object.entries(templates)) { compiled[name] = this.compile(template); } return compiled; } } // Template literal helper for MusPE templates function template(strings, ...values) { let result = ''; for (let i = 0; i < strings.length; i++) { result += strings[i]; if (i < values.length) { result += `{{ ${values[i]} }}`; } } return result; } // Directive helper functions const directiveHelpers = { // v-if helper vIf: (condition, element) => { return condition ? element : null; }, // v-for helper vFor: (items, renderFn) => { if (!Array.isArray(items)) return []; return items.map((item, index) => renderFn(item, index)); }, // v-show helper vShow: (condition, element) => { if (!element) return null; if (element.props) { element.props.style = { ...element.props.style, display: condition ? '' : 'none' }; } return element; } }; // Create global compiler instance const compiler = new MusPETemplateCompiler(); // Export for module usage if (typeof module !== 'undefined' && module.exports) { module.exports = { MusPETemplateCompiler, compiler, template, directiveHelpers }; } // Make available globally if (typeof window !== 'undefined') { window.MusPETemplateCompiler = MusPETemplateCompiler; window.compiler = compiler; window.template = template; window.directiveHelpers = directiveHelpers; }