@razen-core/zenweb
Version:
A minimalist TypeScript framework for building reactive web applications with no virtual DOM
204 lines • 8.57 kB
JavaScript
/**
* ZenWeb Builder
* Handles compilation and bundling
*/
import * as esbuild from 'esbuild';
import * as fs from 'fs';
import * as path from 'path';
import { parseZenWebFile, transformImports } from './parser.js';
import { logger } from './logger.js';
/**
* Copy files from public directory to dist
*/
function copyPublicFiles(sourceDir, destDir) {
const files = fs.readdirSync(sourceDir);
for (const file of files) {
const sourcePath = path.join(sourceDir, file);
const destPath = path.join(destDir, file);
const stat = fs.statSync(sourcePath);
if (stat.isDirectory()) {
// Recursively copy directories
if (!fs.existsSync(destPath)) {
fs.mkdirSync(destPath, { recursive: true });
}
copyPublicFiles(sourcePath, destPath);
}
else {
// Copy files (skip styles.css as it's handled separately)
if (file !== 'styles.css') {
fs.copyFileSync(sourcePath, destPath);
logger.debug(`Copied: ${file}`);
}
}
}
}
/**
* Build a ZenWeb project
*/
export async function build(options) {
const isDebug = process.argv.includes('--debug');
if (isDebug) {
logger.setDebug(true);
}
logger.info('Building ZenWeb project');
logger.debug(`Build options: ${JSON.stringify(options)}`);
const projectRoot = process.cwd();
const srcDir = path.join(projectRoot, 'src');
const distDir = path.join(projectRoot, path.dirname(options.output));
logger.debug(`Project root: ${projectRoot}`);
logger.debug(`Source directory: ${srcDir}`);
logger.debug(`Output directory: ${distDir}`);
// Ensure dist directory exists
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir, { recursive: true });
}
// Collect all styles
let allStyles = '';
// Create esbuild plugin for ZenWeb transformation
const zenwebPlugin = {
name: 'zenweb-transform',
setup(build) {
build.onLoad({ filter: /\.(ts|js|tsx|jsx)$/ }, async (args) => {
logger.debug(`Processing file: ${args.path}`);
const source = await fs.promises.readFile(args.path, 'utf8');
// Check if file contains view or style keywords
if (source.includes('view {') || source.includes('style {')) {
logger.debug(`Found view/style keywords in: ${args.path}`);
const parsed = parseZenWebFile(source);
if (parsed.styles) {
logger.debug(`Extracted ${parsed.styles.length} chars of styles from: ${args.path}`);
allStyles += parsed.styles;
}
let transformedCode = parsed.code;
transformedCode = transformImports(transformedCode);
logger.debug(`Transformed file: ${args.path}`);
return {
contents: transformedCode,
loader: args.path.endsWith('.ts') || args.path.endsWith('.tsx') ? 'ts' : 'js'
};
}
// Transform imports even if no view/style
logger.debug(`No view/style keywords in: ${args.path}`);
const transformedCode = transformImports(source);
return {
contents: transformedCode,
loader: args.path.endsWith('.ts') || args.path.endsWith('.tsx') ? 'ts' : 'js'
};
});
}
};
try {
logger.debug(`Starting esbuild with entry: ${options.entry}`);
// Build with esbuild
const result = await esbuild.build({
entryPoints: [path.join(projectRoot, options.entry)],
bundle: true,
outfile: path.join(projectRoot, options.output),
format: 'esm',
platform: 'browser',
target: 'es2020',
minify: options.minify,
sourcemap: options.sourceMaps,
plugins: [zenwebPlugin],
external: [],
write: true,
logLevel: isDebug ? 'debug' : 'warning'
});
logger.debug(`esbuild completed successfully`);
logger.debug(`Output files: ${result.outputFiles?.length || 'written to disk'}`);
// Copy public files to dist
const publicDir = path.join(projectRoot, 'public');
if (fs.existsSync(publicDir)) {
logger.debug(`Copying public files from ${publicDir} to ${distDir}`);
copyPublicFiles(publicDir, distDir);
logger.success('Public files copied to dist');
}
// Handle styles.css
const publicStylesPath = path.join(publicDir, 'styles.css');
const distStylesPath = path.join(distDir, 'styles.css');
if (allStyles) {
// If we extracted styles from components, write them
logger.debug(`Writing ${allStyles.length} chars of extracted CSS to ${distStylesPath}`);
await fs.promises.writeFile(distStylesPath, allStyles, 'utf8');
logger.success('Component styles written to dist/styles.css');
}
else if (!fs.existsSync(distStylesPath)) {
// If no extracted styles and no styles.css exists, create empty one
logger.debug('No styles found, creating empty styles.css');
const defaultStyles = `/* ZenWeb Styles */\n* {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n}\n\nbody {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n}\n`;
await fs.promises.writeFile(distStylesPath, defaultStyles, 'utf8');
logger.success('Created default styles.css');
}
logger.success(`Build complete: ${options.output}`);
if (result.warnings.length > 0) {
logger.warning(`Build warnings: ${result.warnings.length} warning(s)`);
result.warnings.forEach(w => {
logger.debug(`Warning: ${w.text}`);
if (w.location) {
logger.debug(` at ${w.location.file}:${w.location.line}:${w.location.column}`);
}
});
}
else {
logger.debug(`No build warnings`);
}
if (result.errors.length > 0) {
logger.error(`Build errors: ${result.errors.length} error(s)`);
result.errors.forEach(e => {
logger.error(`Error: ${e.text}`);
if (e.location) {
logger.error(` at ${e.location.file}:${e.location.line}:${e.location.column}`);
}
});
}
}
catch (error) {
logger.error('Build failed', error);
logger.debug(`Error details: ${JSON.stringify(error, null, 2)}`);
throw error;
}
}
/**
* Watch mode for development
*/
export async function watch(options) {
logger.info('Watching for changes');
// Use esbuild's watch mode
const projectRoot = process.cwd();
const zenwebPlugin = {
name: 'zenweb-transform',
setup(build) {
build.onLoad({ filter: /\.(ts|js|tsx|jsx)$/ }, async (args) => {
const source = await fs.promises.readFile(args.path, 'utf8');
if (source.includes('view {') || source.includes('style {')) {
const parsed = parseZenWebFile(source);
let transformedCode = parsed.code;
transformedCode = transformImports(transformedCode);
return {
contents: transformedCode,
loader: args.path.endsWith('.ts') || args.path.endsWith('.tsx') ? 'ts' : 'js'
};
}
const transformedCode = transformImports(source);
return {
contents: transformedCode,
loader: args.path.endsWith('.ts') || args.path.endsWith('.tsx') ? 'ts' : 'js'
};
});
}
};
const ctx = await esbuild.context({
entryPoints: [path.join(projectRoot, options.entry)],
bundle: true,
outfile: path.join(projectRoot, options.output),
format: 'esm',
platform: 'browser',
target: 'es2020',
minify: false,
sourcemap: true,
plugins: [zenwebPlugin]
});
await ctx.watch();
logger.success('Watch mode enabled');
}
//# sourceMappingURL=builder-backup.js.map