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
JavaScript
// 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;
}