@ordojs/core
Version:
Core compiler and runtime for OrdoJS framework
267 lines • 9.41 kB
JavaScript
/**
* @fileoverview CSS-in-JS Compiler for OrdoJS Framework
* Handles compilation of CSS-in-JS expressions to optimized CSS
*/
import {} from '../types/index.js';
/**
* Default CSS-in-JS options
*/
const DEFAULT_CSS_IN_JS_OPTIONS = {
scoped: true,
classPrefix: 'ordojs-css',
optimize: true,
generateSourceMaps: true
};
/**
* CSS-in-JS Compiler for OrdoJS components
*/
export class OrdoJSCSSInJSCompiler {
options;
classCounter = 0;
constructor(options = {}) {
this.options = { ...DEFAULT_CSS_IN_JS_OPTIONS, ...options };
}
/**
* Compile CSS-in-JS expression to CSS
*/
compile(expression, componentName) {
const className = this.generateClassName(componentName);
const declarations = this.compileExpression(expression);
const rule = {
type: 'CSSRule',
selector: `.${className}`,
declarations,
range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
};
const styleBlock = {
type: 'StyleBlock',
rules: [rule],
scoped: this.options.scoped || false,
range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
};
const css = this.generateCSS(styleBlock);
const dependencies = this.extractDependencies(expression);
return {
css,
className,
styleBlock,
dependencies
};
}
/**
* Compile CSS-in-JS expression to CSS declarations
*/
compileExpression(expression) {
switch (expression.type) {
case 'CSSObject':
return this.compileCSSObject(expression);
case 'CSSTemplate':
return this.compileCSSTemplate(expression);
case 'CSSFunction':
return this.compileCSSFunction(expression);
default:
throw new Error(`Unsupported CSS-in-JS expression type: ${expression.type}`);
}
}
/**
* Compile CSS object expression
*/
compileCSSObject(expression) {
const declarations = [];
for (const property of expression.properties) {
if (typeof property.value === 'object' && property.value.type === 'CSSObject') {
// Nested object - handle pseudo-classes, media queries, etc.
const nestedDeclarations = this.compileCSSObject(property.value);
// For now, we'll flatten nested objects - in a real implementation,
// we'd need to handle nesting properly
declarations.push(...nestedDeclarations);
}
else {
const cssProperty = this.camelCaseToKebabCase(property.key);
const cssValue = this.normalizeCSSValue(property.value);
declarations.push({
type: 'CSSDeclaration',
property: cssProperty,
value: cssValue,
important: false,
range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
});
}
}
return declarations;
}
/**
* Compile CSS template expression
*/
compileCSSTemplate(expression) {
// Parse the template string as CSS
let cssText = expression.template;
// Replace template expressions with their values
// This is a simplified implementation - in reality, we'd need proper template parsing
expression.expressions.forEach((expr, index) => {
const placeholder = `\${${index}}`;
if (cssText.includes(placeholder)) {
// For now, we'll assume expressions evaluate to strings
// In a real implementation, we'd need to evaluate the expressions
cssText = cssText.replace(placeholder, `/* expression ${index} */`);
}
});
return this.parseCSSDeclarations(cssText);
}
/**
* Compile CSS function expression
*/
compileCSSFunction(expression) {
const declarations = [];
for (const arg of expression.arguments) {
if (typeof arg === 'string') {
declarations.push(...this.parseCSSDeclarations(arg));
}
else {
declarations.push(...this.compileExpression(arg));
}
}
return declarations;
}
/**
* Parse CSS declarations from a string
*/
parseCSSDeclarations(cssText) {
const declarations = [];
// Split by semicolons and parse each declaration
const declarationStrings = cssText.split(';').map(s => s.trim()).filter(Boolean);
for (const declStr of declarationStrings) {
const colonIndex = declStr.indexOf(':');
if (colonIndex > 0) {
const property = declStr.substring(0, colonIndex).trim();
const value = declStr.substring(colonIndex + 1).trim();
if (property && value) {
declarations.push({
type: 'CSSDeclaration',
property,
value,
important: value.includes('!important'),
range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
});
}
}
}
return declarations;
}
/**
* Convert camelCase to kebab-case for CSS properties
*/
camelCaseToKebabCase(str) {
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
}
/**
* Normalize CSS value (handle numbers, add units, etc.)
*/
normalizeCSSValue(value) {
if (typeof value === 'number') {
// Add 'px' unit to numeric values for properties that need units
return `${value}px`;
}
if (typeof value === 'string') {
return value;
}
// For nested objects, we'd need to handle them differently
return 'inherit';
}
/**
* Generate unique class name
*/
generateClassName(componentName) {
const prefix = this.options.classPrefix || 'ordojs-css';
const suffix = componentName ? `${componentName.toLowerCase()}-${this.classCounter++}` : `${this.classCounter++}`;
return `${prefix}-${suffix}`;
}
/**
* Extract dependencies from CSS-in-JS expression
*/
extractDependencies(expression) {
const dependencies = [];
// This is a simplified implementation
// In a real implementation, we'd analyze the expression for variable references
return dependencies;
}
/**
* Generate CSS string from StyleBlockNode
*/
generateCSS(styleBlock) {
return styleBlock.rules.map(rule => {
const declarations = rule.declarations.map(decl => ` ${decl.property}: ${decl.value};`).join('\n');
return `${rule.selector} {\n${declarations}\n}`;
}).join('\n\n');
}
/**
* Compile multiple CSS-in-JS expressions and merge them
*/
compileMultiple(expressions, componentName) {
const allDeclarations = [];
const allDependencies = [];
for (const expression of expressions) {
const declarations = this.compileExpression(expression);
const dependencies = this.extractDependencies(expression);
allDeclarations.push(...declarations);
allDependencies.push(...dependencies);
}
const className = this.generateClassName(componentName);
const rule = {
type: 'CSSRule',
selector: `.${className}`,
declarations: allDeclarations,
range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
};
const styleBlock = {
type: 'StyleBlock',
rules: [rule],
scoped: this.options.scoped || false,
range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
};
const css = this.generateCSS(styleBlock);
return {
css,
className,
styleBlock,
dependencies: [...new Set(allDependencies)]
};
}
/**
* Create CSS-in-JS expression from JavaScript object
*/
static createCSSObject(obj) {
const properties = Object.entries(obj).map(([key, value]) => ({
key,
value: typeof value === 'object' && value !== null
? OrdoJSCSSInJSCompiler.createCSSObject(value)
: value,
computed: false
}));
return {
type: 'CSSObject',
properties
};
}
/**
* Create CSS template expression
*/
static createCSSTemplate(template, expressions = []) {
return {
type: 'CSSTemplate',
template,
expressions
};
}
/**
* Create CSS function expression
*/
static createCSSFunction(functionName, args) {
return {
type: 'CSSFunction',
functionName,
arguments: args
};
}
}
//# sourceMappingURL=css-in-js-compiler.js.map