@ordojs/core
Version:
Core compiler and runtime for OrdoJS framework
533 lines • 19.8 kB
JavaScript
/**
* @fileoverview OrdoJS Code Generator - Refactored modular implementation
* @author OrdoJS Framework Team
*/
/**
* Source map builder utility
*/
export class SourceMapBuilder {
segments = [];
sources = [];
names = [];
addMapping(generatedLine, generatedColumn, originalLine, originalColumn, source, name) {
if (!this.sources.includes(source)) {
this.sources.push(source);
}
if (name && !this.names.includes(name)) {
this.names.push(name);
}
this.segments.push({
generatedLine,
generatedColumn,
originalLine,
originalColumn,
source,
name
});
}
build() {
return {
version: 3,
sources: this.sources,
names: this.names,
mappings: this.encodeMappings(),
sourcesContent: []
};
}
encodeMappings() {
// Simplified VLQ encoding - in production, use proper source map library
return this.segments
.map(seg => `${seg.generatedLine},${seg.generatedColumn},${seg.originalLine},${seg.originalColumn}`)
.join(';');
}
}
/**
* Enhanced OrdoJS Code Generator with modular architecture
*/
export class OrdoJSCodeGenerator {
options;
transformers;
constructor(options = {}) {
this.options = {
target: 'development',
minify: false,
sourceMaps: true,
esTarget: 'es2022',
treeShaking: true,
transformers: [],
format: 'esm',
hmr: false,
splitting: 'none',
...options
};
this.transformers = new Map([
['pre', []],
['post', []],
['optimize', []]
]);
this.registerDefaultTransformers();
this.registerCustomTransformers();
}
/**
* Generate code for all targets
*/
generate(ast) {
const metadata = this.extractMetadata(ast);
const context = this.createTransformContext(ast, metadata);
const results = {};
// Generate client code
if (metadata.hasClientCode || ast.component.markupBlock) {
results.client = this.generateClientCode(ast, context);
}
// Generate server code
if (metadata.hasServerCode) {
results.server = this.generateServerCode(ast, context);
}
// Generate HTML
results.html = this.generateHTML(ast, context);
// Generate CSS
if (metadata.hasStyles) {
results.css = this.generateCSS(ast, context);
}
// Generate source map
if (this.options.sourceMaps) {
results.sourceMap = context.sourceMapBuilder?.build() || this.createEmptySourceMap();
}
return results;
}
/**
* Generate client-side code
*/
generateClientCode(ast, context) {
const genContext = this.createCodeGenContext();
this.generateClientImports(ast, genContext);
this.generateClientComponent(ast.component, genContext);
this.generateClientExports(ast, genContext);
let code = genContext.lines.join('\n');
// Apply transformers
code = this.applyTransformers(code, { ...context, target: 'client' });
return code;
}
/**
* Generate server-side code
*/
generateServerCode(ast, context) {
if (!ast.component.serverBlock) {
return '';
}
const genContext = this.createCodeGenContext();
this.generateServerImports(ast, genContext);
this.generateServerFunctions(ast.component.serverBlock, genContext);
this.generateServerExports(ast, genContext);
let code = genContext.lines.join('\n');
// Apply transformers
code = this.applyTransformers(code, { ...context, target: 'server' });
return code;
}
/**
* Generate HTML template
*/
generateHTML(ast, context, props = {}) {
const genContext = this.createCodeGenContext();
this.generateHTMLStructure(ast.component.markupBlock, genContext, props);
let code = genContext.lines.join('\n');
// Apply transformers
code = this.applyTransformers(code, { ...context, target: 'static' });
return code;
}
/**
* Generate CSS styles
*/
generateCSS(ast, context) {
// CSS generation would be implemented here
return '';
}
/**
* Add custom transformer
*/
addTransformer(transformer) {
const phaseTransformers = this.transformers.get(transformer.phase) || [];
phaseTransformers.push(transformer);
this.transformers.set(transformer.phase, phaseTransformers);
}
/**
* Remove transformer by name
*/
removeTransformer(name) {
for (const [phase, transformers] of this.transformers) {
const index = transformers.findIndex(t => t.name === name);
if (index >= 0) {
transformers.splice(index, 1);
return true;
}
}
return false;
}
registerDefaultTransformers() {
// Minification transformer
this.addTransformer({
name: 'minifier',
phase: 'optimize',
targets: ['client', 'server'],
transform: (code, context) => {
if (!context.options.minify) {
return { code };
}
// Simple minification - in production, use proper minifier
const minified = code
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments
.replace(/\/\/.*$/gm, '') // Remove single-line comments
.replace(/\s+/g, ' ') // Collapse whitespace
.replace(/;\s*}/g, '}') // Remove unnecessary semicolons
.trim();
return { code: minified };
}
});
// ES target transformer
this.addTransformer({
name: 'es-target',
phase: 'post',
targets: ['client', 'server'],
transform: (code, context) => {
if (context.options.esTarget === 'es2022') {
return { code };
}
// Transform modern JS to older versions
let transformed = code;
if (context.options.esTarget === 'es2018' || context.options.esTarget === 'es5') {
// Transform arrow functions to regular functions
transformed = transformed.replace(/(\w+)\s*=>\s*{([^}]*)}/g, 'function($1) { $2 }');
// Transform const/let to var for ES5
if (context.options.esTarget === 'es5') {
transformed = transformed.replace(/\b(const|let)\b/g, 'var');
}
}
return { code: transformed };
}
});
// HMR transformer
this.addTransformer({
name: 'hmr',
phase: 'post',
targets: ['client'],
transform: (code, context) => {
if (!context.options.hmr || context.options.target === 'production') {
return { code };
}
const hmrCode = `
// Hot Module Replacement
if (module.hot) {
module.hot.accept();
module.hot.dispose(() => {
// Cleanup code
});
}
`;
return { code: code + '\n' + hmrCode };
}
});
}
registerCustomTransformers() {
// Register any custom transformers from options
for (const transformer of this.options.transformers) {
this.addTransformer(transformer);
}
}
extractMetadata(ast) {
const component = ast.component;
return {
name: component.name,
hasClientCode: !!component.clientBlock,
hasServerCode: !!component.serverBlock,
hasStyles: false, // Would check for style blocks
dependencies: ast.dependencies,
exports: ast.exports,
props: component.props.map(p => p.name),
state: component.clientBlock?.reactiveVariables.map(v => v.name) || [],
functions: [
...(component.clientBlock?.functions.map(f => f.name) || []),
...(component.serverBlock?.functions.map(f => f.name) || [])
],
lifecycle: component.clientBlock?.lifecycle.map(l => l.hookType.toString()) || []
};
}
createTransformContext(ast, metadata) {
return {
ast,
options: this.options,
target: 'client',
metadata,
sourceMapBuilder: this.options.sourceMaps ? new SourceMapBuilder() : undefined
};
}
createCodeGenContext() {
return {
indent: 0,
lines: [],
sourceMap: this.options.sourceMaps ? new SourceMapBuilder() : undefined,
scopes: new Map(),
scopeDepth: 0,
imports: new Map(),
exports: new Set()
};
}
generateClientImports(ast, context) {
// Generate import statements
if (this.options.format === 'esm') {
context.lines.push("import { createSignal, createEffect } from '@ordojs/runtime';");
}
else {
context.lines.push("const { createSignal, createEffect } = require('@ordojs/runtime');");
}
context.lines.push('');
}
generateClientComponent(component, context) {
const componentName = component.name;
// Function declaration
context.lines.push(`function ${componentName}(props = {}) {`);
context.indent++;
// Generate reactive variables
if (component.clientBlock?.reactiveVariables) {
for (const variable of component.clientBlock.reactiveVariables) {
this.generateReactiveVariable(variable, context);
}
context.lines.push('');
}
// Generate functions
if (component.clientBlock?.functions) {
for (const func of component.clientBlock.functions) {
this.generateFunction(func, context);
}
context.lines.push('');
}
// Generate lifecycle hooks
if (component.clientBlock?.lifecycle) {
for (const hook of component.clientBlock.lifecycle) {
this.generateLifecycleHook(hook, context);
}
context.lines.push('');
}
// Generate render function
this.generateRenderFunction(component.markupBlock, context);
// Return component API
context.lines.push(' return {');
context.lines.push(' mount,');
context.lines.push(' unmount,');
context.lines.push(' render,');
context.lines.push(' getState: () => ({ ...state }),');
context.lines.push(' setState: (key, value) => { state[key] = value; }');
context.lines.push(' };');
context.indent--;
context.lines.push('}');
context.lines.push('');
}
generateReactiveVariable(variable, context) {
const indent = ' '.repeat(context.indent);
const initialValue = this.generateExpression(variable.initialValue);
if (variable.isConst) {
context.lines.push(`${indent}const ${variable.name} = ${initialValue};`);
}
else {
context.lines.push(`${indent}const [${variable.name}, set${this.capitalize(variable.name)}] = createSignal(${initialValue});`);
}
}
generateFunction(func, context) {
const indent = ' '.repeat(context.indent);
const params = func.parameters.map((p) => p.name).join(', ');
context.lines.push(`${indent}function ${func.name}(${params}) {`);
for (const statement of func.body) {
this.generateStatement(statement, context);
}
context.lines.push(`${indent}}`);
}
generateLifecycleHook(hook, context) {
const indent = ' '.repeat(context.indent);
context.lines.push(`${indent}createEffect(() => {`);
for (const statement of hook.handler) {
this.generateStatement(statement, context);
}
context.lines.push(`${indent}});`);
}
generateRenderFunction(markupBlock, context) {
const indent = ' '.repeat(context.indent);
context.lines.push(`${indent}function render() {`);
context.lines.push(`${indent} const element = document.createElement('div');`);
context.lines.push(`${indent} element.className = 'ordojs-component';`);
// Generate markup rendering logic
for (const element of markupBlock.elements) {
this.generateHTMLElement(element, context);
}
context.lines.push(`${indent} return element;`);
context.lines.push(`${indent}}`);
context.lines.push('');
context.lines.push(`${indent}function mount(target) {`);
context.lines.push(`${indent} const element = render();`);
context.lines.push(`${indent} target.appendChild(element);`);
context.lines.push(`${indent}}`);
context.lines.push('');
context.lines.push(`${indent}function unmount() {`);
context.lines.push(`${indent} // Cleanup logic`);
context.lines.push(`${indent}}`);
}
generateHTMLElement(element, context) {
const indent = ' '.repeat(context.indent + 1);
context.lines.push(`${indent}// HTML element: ${element.tagName || 'div'}`);
}
generateStatement(statement, context) {
const indent = ' '.repeat(context.indent + 1);
if (statement.expression) {
const expr = this.generateExpression(statement.expression);
context.lines.push(`${indent}${expr};`);
}
}
generateExpression(expression) {
switch (expression.expressionType) {
case 'LITERAL':
return JSON.stringify(expression.value);
case 'IDENTIFIER':
return expression.identifier || 'undefined';
case 'BINARY':
const left = this.generateExpression(expression.left);
const right = this.generateExpression(expression.right);
return `${left} ${expression.operator} ${right}`;
case 'CALL':
const callee = this.generateExpression(expression.callee);
const args = expression.arguments?.map(arg => this.generateExpression(arg)).join(', ') || '';
return `${callee}(${args})`;
default:
return 'undefined';
}
}
generateServerImports(ast, context) {
context.lines.push("const { createServerFunction } = require('@ordojs/runtime');");
context.lines.push('');
}
generateServerFunctions(serverBlock, context) {
for (const func of serverBlock.functions) {
this.generateServerFunction(func, context);
}
}
generateServerFunction(func, context) {
const params = func.parameters.map((p) => p.name).join(', ');
const visibility = func.isPublic ? 'public' : 'private';
context.lines.push(`// ${visibility} server function`);
context.lines.push(`async function ${func.name}(${params}) {`);
for (const statement of func.body) {
this.generateStatement(statement, context);
}
context.lines.push('}');
context.lines.push('');
if (func.isPublic) {
context.exports.add(func.name);
}
}
generateServerExports(ast, context) {
if (context.exports.size > 0) {
const exports = Array.from(context.exports).join(', ');
if (this.options.format === 'esm') {
context.lines.push(`export { ${exports} };`);
}
else {
context.lines.push(`module.exports = { ${exports} };`);
}
}
}
generateClientExports(ast, context) {
const componentName = ast.component.name;
if (this.options.format === 'esm') {
context.lines.push(`export default ${componentName};`);
context.lines.push(`export { ${componentName} };`);
}
else if (this.options.format === 'cjs') {
context.lines.push(`module.exports = ${componentName};`);
context.lines.push(`module.exports.${componentName} = ${componentName};`);
}
else if (this.options.format === 'umd') {
context.lines.push(`
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.OrdoJS = {}));
}(this, (function (exports) {
exports.${componentName} = ${componentName};
})));`);
}
else if (this.options.format === 'iife') {
context.lines.push(`window.${componentName} = ${componentName};`);
}
}
generateHTMLStructure(markupBlock, context, props) {
context.lines.push('<div class="ordojs-component">');
// Generate HTML from markup block
for (const element of markupBlock.elements) {
context.lines.push(` <!-- ${element.tagName || 'element'} -->`);
context.lines.push(' <div>Generated HTML content</div>');
}
context.lines.push('</div>');
}
applyTransformers(code, context) {
const phases = ['pre', 'post', 'optimize'];
for (const phase of phases) {
const transformers = this.transformers.get(phase) || [];
for (const transformer of transformers) {
if (transformer.targets.includes(context.target)) {
const result = transformer.transform(code, context);
code = result.code;
}
}
}
return code;
}
createEmptySourceMap() {
return {
version: 3,
sources: [],
names: [],
mappings: '',
sourcesContent: []
};
}
capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
}
/**
* Default code transformers
*/
export const defaultTransformers = [
{
name: 'import-optimizer',
phase: 'optimize',
targets: ['client', 'server'],
transform: (code, context) => {
// Remove unused imports
const lines = code.split('\n');
const optimizedLines = lines.filter(line => {
if (line.trim().startsWith('import') || line.trim().startsWith('const')) {
// Simple check - in production, use proper AST analysis
return true;
}
return true;
});
return { code: optimizedLines.join('\n') };
}
},
{
name: 'dead-code-eliminator',
phase: 'optimize',
targets: ['client', 'server'],
transform: (code, context) => {
if (!context.options.treeShaking) {
return { code };
}
// Simple dead code elimination - in production, use proper analysis
const lines = code.split('\n');
const optimizedLines = lines.filter(line => {
const trimmed = line.trim();
return trimmed !== '' && !trimmed.startsWith('//');
});
return { code: optimizedLines.join('\n') };
}
}
];
//# sourceMappingURL=code-generator-refactored.js.map