UNPKG

@kalxjs/compiler-plugin

Version:

Webpack and Rollup plugin for KalxJS single-file components

266 lines (227 loc) 7.55 kB
/** * @kalxjs/compiler-plugin - Unified KLX File Compiler * Shared compilation logic for all build tool plugins * * @module @kalxjs/compiler-plugin/compile-klx */ import { parse } from '@kalxjs/compiler'; import { processScriptSetup, compileMacros } from '../../compiler/src/script-setup/index.js'; import { compileTypeScript, isTypeScript } from '../../compiler/src/typescript/index.js'; import { processCSSModule, isCSSModule, generateCSSModuleExports } from '../../compiler/src/css/modules.js'; import { processWithPreprocessor, detectPreprocessor } from '../../compiler/src/css/preprocessors.js'; /** * Compile .klx file to JavaScript * @param {string} source - Source code * @param {object} options - Compilation options * @returns {Promise<object>} - Compiled result */ export async function compileKLXFile(source, options = {}) { const { filename = 'anonymous.klx', sourceMap = false, hotReload = false, isProduction = false } = options; console.log('[compile-klx] Compiling file:', filename); try { // Parse .klx file const descriptor = parseKLXFile(source); // Process script let scriptCode = ''; let scriptSetupResult = null; if (descriptor.scriptSetup) { // Handle <script setup> scriptSetupResult = processScriptSetup(descriptor.scriptSetup.content, { filename }); scriptCode = compileMacros(scriptSetupResult); } else if (descriptor.script) { // Handle regular <script> const { content, attrs } = descriptor.script; // Check for TypeScript if (isTypeScript(content, attrs)) { const tsResult = compileTypeScript(content, { filename, sourceMap }); scriptCode = tsResult.code; } else { scriptCode = content; } } // Process template let templateCode = ''; if (descriptor.template) { // TODO: Use actual template compiler templateCode = `function render() { return ${JSON.stringify(descriptor.template.content)}; }`; } // Process styles let stylesCode = ''; const cssModules = {}; for (let i = 0; i < descriptor.styles.length; i++) { const style = descriptor.styles[i]; let css = style.content; // Handle preprocessors const preprocessor = detectPreprocessor(style.attrs); if (preprocessor) { const result = await processWithPreprocessor(css, { lang: preprocessor, filename, sourceMap }); css = result.css; } // Handle CSS modules if (isCSSModule(style.attrs)) { const moduleResult = processCSSModule(css, { filename }); css = moduleResult.css; cssModules[`style${i}`] = moduleResult.exports; } // Handle scoped styles if (style.scoped) { const scopeId = generateScopeId(filename); css = scopeStyles(css, scopeId); } stylesCode += css; } // Generate final component code const code = generateFinalCode({ script: scriptCode, template: templateCode, styles: stylesCode, cssModules, filename, hotReload, isProduction }); console.log('[compile-klx] Compilation successful'); return { code, map: sourceMap ? generateSourceMap(code, filename) : null, dependencies: [] }; } catch (error) { console.error('[compile-klx] Compilation error:', error); throw error; } } /** * Parse .klx file into descriptor */ function parseKLXFile(source) { const descriptor = { template: null, script: null, scriptSetup: null, styles: [] }; // Parse template const templateMatch = source.match(/<template>([\s\S]*?)<\/template>/); if (templateMatch) { descriptor.template = { content: templateMatch[1].trim() }; } // Parse script setup const scriptSetupMatch = source.match(/<script\s+setup([^>]*)>([\s\S]*?)<\/script>/); if (scriptSetupMatch) { descriptor.scriptSetup = { content: scriptSetupMatch[2].trim(), attrs: parseAttrs(scriptSetupMatch[1]) }; } else { // Parse regular script const scriptMatch = source.match(/<script([^>]*)>([\s\S]*?)<\/script>/); if (scriptMatch) { descriptor.script = { content: scriptMatch[2].trim(), attrs: parseAttrs(scriptMatch[1]) }; } } // Parse styles const styleMatches = [...source.matchAll(/<style([^>]*)>([\s\S]*?)<\/style>/g)]; descriptor.styles = styleMatches.map(match => ({ content: match[2].trim(), attrs: parseAttrs(match[1]), scoped: match[1].includes('scoped'), module: match[1].includes('module') })); return descriptor; } /** * Parse attributes from tag */ function parseAttrs(attrString) { const attrs = {}; const regex = /(\w+)(?:="([^"]*)")?/g; let match; while ((match = regex.exec(attrString)) !== null) { attrs[match[1]] = match[2] || true; } return attrs; } /** * Generate scope ID for scoped styles */ function generateScopeId(filename) { const hash = filename.split('').reduce((a, b) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0); return `data-v-${Math.abs(hash).toString(16)}`; } /** * Scope CSS with data attribute */ function scopeStyles(css, scopeId) { return css.replace(/([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)/g, (match, selector) => { selector = selector.trim(); return `${selector}[${scopeId}]${match.slice(selector.length)}`; }); } /** * Generate final component code */ function generateFinalCode(options) { const { script, template, styles, cssModules, filename, hotReload, isProduction } = options; let code = ''; // Add imports code += `import { defineComponent } from '@kalxjs/core';\n\n`; // Add script code if (script) { code += `${script}\n\n`; } // Add template if (template) { code += `const render = ${template};\n\n`; } // Add CSS modules exports if (Object.keys(cssModules).length > 0) { code += `const __cssModules = ${JSON.stringify(cssModules)};\n\n`; } // Inject styles (runtime) if (styles && !isProduction) { code += `if (typeof document !== 'undefined') {\n`; code += ` const style = document.createElement('style');\n`; code += ` style.textContent = ${JSON.stringify(styles)};\n`; code += ` document.head.appendChild(style);\n`; code += `}\n\n`; } // Add HMR code if (hotReload) { code += `if (import.meta.hot) {\n`; code += ` import.meta.hot.accept();\n`; code += `}\n\n`; } return code; } /** * Generate source map */ function generateSourceMap(code, filename) { return { version: 3, file: filename, sources: [filename], sourcesContent: [code], names: [], mappings: '' }; } export default { compileKLXFile };