@zenithbuild/core 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/commands/dev.ts +56 -14
- package/compiler/css/index.ts +317 -0
- package/compiler/ssg-build.ts +17 -11
- package/dist/cli.js +2 -0
- package/dist/zen-build.js +6937 -3488
- package/dist/zen-dev.js +6937 -3488
- package/dist/zen-preview.js +6937 -3488
- package/dist/zenith.js +6937 -3488
- package/package.json +1 -1
package/cli/commands/dev.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { loadContent } from '../utils/content'
|
|
|
13
13
|
import { loadZenithConfig } from '../../core/config/loader'
|
|
14
14
|
import { PluginRegistry, createPluginContext } from '../../core/plugins/registry'
|
|
15
15
|
import type { ContentItem } from '../../core/config/types'
|
|
16
|
+
import { compileCssAsync, resolveGlobalsCss } from '../../compiler/css'
|
|
16
17
|
|
|
17
18
|
export interface DevOptions {
|
|
18
19
|
port?: number
|
|
@@ -67,6 +68,23 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
67
68
|
|
|
68
69
|
console.log('[Zenith] Content collections loaded:', Object.keys(contentData))
|
|
69
70
|
|
|
71
|
+
// ============================================
|
|
72
|
+
// CSS Compilation (Compiler-Owned)
|
|
73
|
+
// ============================================
|
|
74
|
+
const globalsCssPath = resolveGlobalsCss(rootDir)
|
|
75
|
+
let compiledCss = ''
|
|
76
|
+
|
|
77
|
+
if (globalsCssPath) {
|
|
78
|
+
console.log('[Zenith] Compiling CSS:', path.relative(rootDir, globalsCssPath))
|
|
79
|
+
const cssResult = await compileCssAsync({ input: globalsCssPath, output: ':memory:' })
|
|
80
|
+
if (cssResult.success) {
|
|
81
|
+
compiledCss = cssResult.css
|
|
82
|
+
console.log(`[Zenith] CSS compiled in ${cssResult.duration}ms`)
|
|
83
|
+
} else {
|
|
84
|
+
console.error('[Zenith] CSS compilation failed:', cssResult.error)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
70
88
|
const clients = new Set<ServerWebSocket<unknown>>()
|
|
71
89
|
|
|
72
90
|
// Branded Startup Panel
|
|
@@ -120,22 +138,38 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
120
138
|
}
|
|
121
139
|
|
|
122
140
|
// Set up file watching for HMR
|
|
123
|
-
const watcher = fs.watch(path.join(pagesDir, '..'), { recursive: true }, (event, filename) => {
|
|
141
|
+
const watcher = fs.watch(path.join(pagesDir, '..'), { recursive: true }, async (event, filename) => {
|
|
124
142
|
if (!filename) return
|
|
125
143
|
|
|
126
144
|
if (filename.endsWith('.zen')) {
|
|
127
145
|
logger.hmr('Page', filename)
|
|
128
|
-
|
|
146
|
+
|
|
147
|
+
// Clear page cache to force fresh compilation on next request
|
|
148
|
+
pageCache.clear()
|
|
149
|
+
|
|
150
|
+
// Recompile CSS for new Tailwind classes in .zen files
|
|
151
|
+
if (globalsCssPath) {
|
|
152
|
+
const cssResult = await compileCssAsync({ input: globalsCssPath, output: ':memory:' })
|
|
153
|
+
if (cssResult.success) {
|
|
154
|
+
compiledCss = cssResult.css
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Broadcast page reload AFTER cache cleared and CSS ready
|
|
129
159
|
for (const client of clients) {
|
|
130
160
|
client.send(JSON.stringify({ type: 'reload' }))
|
|
131
161
|
}
|
|
132
162
|
} else if (filename.endsWith('.css')) {
|
|
133
163
|
logger.hmr('CSS', filename)
|
|
164
|
+
// Recompile CSS
|
|
165
|
+
if (globalsCssPath) {
|
|
166
|
+
const cssResult = await compileCssAsync({ input: globalsCssPath, output: ':memory:' })
|
|
167
|
+
if (cssResult.success) {
|
|
168
|
+
compiledCss = cssResult.css
|
|
169
|
+
}
|
|
170
|
+
}
|
|
134
171
|
for (const client of clients) {
|
|
135
|
-
client.send(JSON.stringify({
|
|
136
|
-
type: 'style-update',
|
|
137
|
-
url: filename.includes('global.css') ? '/styles/global.css' : `/${filename}`
|
|
138
|
-
}))
|
|
172
|
+
client.send(JSON.stringify({ type: 'style-update', url: '/assets/styles.css' }))
|
|
139
173
|
}
|
|
140
174
|
} else if (filename.startsWith('content') || filename.includes('zenith-docs')) {
|
|
141
175
|
logger.hmr('Content', filename)
|
|
@@ -176,14 +210,22 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
176
210
|
return response
|
|
177
211
|
}
|
|
178
212
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
213
|
+
// Serve compiler-owned CSS (Tailwind compiled)
|
|
214
|
+
if (pathname === '/assets/styles.css') {
|
|
215
|
+
const response = new Response(compiledCss, {
|
|
216
|
+
headers: { 'Content-Type': 'text/css; charset=utf-8' }
|
|
217
|
+
})
|
|
218
|
+
logger.route('GET', pathname, 200, Math.round(performance.now() - startTime), 0, Math.round(performance.now() - startTime))
|
|
219
|
+
return response
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Legacy: also support /styles/globals.css or /styles/global.css for backwards compat
|
|
223
|
+
if (pathname === '/styles/globals.css' || pathname === '/styles/global.css') {
|
|
224
|
+
const response = new Response(compiledCss, {
|
|
225
|
+
headers: { 'Content-Type': 'text/css; charset=utf-8' }
|
|
226
|
+
})
|
|
227
|
+
logger.route('GET', pathname, 200, Math.round(performance.now() - startTime), 0, Math.round(performance.now() - startTime))
|
|
228
|
+
return response
|
|
187
229
|
}
|
|
188
230
|
|
|
189
231
|
// Static files
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zenith CSS Compiler Module
|
|
3
|
+
*
|
|
4
|
+
* Compiler-owned CSS processing that integrates Tailwind CSS v4 JIT
|
|
5
|
+
* at compile time. This module ensures:
|
|
6
|
+
*
|
|
7
|
+
* 1. All CSS is processed at build time (no runtime generation)
|
|
8
|
+
* 2. Tailwind sees all .zen templates for class scanning
|
|
9
|
+
* 3. HMR support for instant CSS updates in dev mode
|
|
10
|
+
* 4. Deterministic, cacheable output for production
|
|
11
|
+
*
|
|
12
|
+
* Per Zenith CSS Directive: The compiler owns styles.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { spawn, spawnSync } from 'child_process'
|
|
16
|
+
import path from 'path'
|
|
17
|
+
import fs from 'fs'
|
|
18
|
+
|
|
19
|
+
// ============================================
|
|
20
|
+
// Types
|
|
21
|
+
// ============================================
|
|
22
|
+
|
|
23
|
+
export interface CSSCompileOptions {
|
|
24
|
+
/** Input CSS file path (e.g., src/styles/globals.css) */
|
|
25
|
+
input: string
|
|
26
|
+
/** Output CSS file path, or ':memory:' for in-memory result */
|
|
27
|
+
output: string
|
|
28
|
+
/** Enable minification for production */
|
|
29
|
+
minify?: boolean
|
|
30
|
+
/** Watch mode for HMR */
|
|
31
|
+
watch?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CSSCompileResult {
|
|
35
|
+
/** Compiled CSS content */
|
|
36
|
+
css: string
|
|
37
|
+
/** Compilation time in milliseconds */
|
|
38
|
+
duration: number
|
|
39
|
+
/** Whether compilation succeeded */
|
|
40
|
+
success: boolean
|
|
41
|
+
/** Error message if failed */
|
|
42
|
+
error?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============================================
|
|
46
|
+
// CSS Compilation
|
|
47
|
+
// ============================================
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Compile CSS using Tailwind CSS v4 CLI
|
|
51
|
+
*
|
|
52
|
+
* This function synchronously compiles CSS for use in:
|
|
53
|
+
* - Dev server startup
|
|
54
|
+
* - SSG build
|
|
55
|
+
* - On-demand recompilation
|
|
56
|
+
*
|
|
57
|
+
* @param options Compilation options
|
|
58
|
+
* @returns Compiled CSS result
|
|
59
|
+
*/
|
|
60
|
+
export function compileCss(options: CSSCompileOptions): CSSCompileResult {
|
|
61
|
+
const startTime = performance.now()
|
|
62
|
+
const { input, output, minify = false } = options
|
|
63
|
+
|
|
64
|
+
// Validate input exists
|
|
65
|
+
if (!fs.existsSync(input)) {
|
|
66
|
+
return {
|
|
67
|
+
css: '',
|
|
68
|
+
duration: 0,
|
|
69
|
+
success: false,
|
|
70
|
+
error: `CSS input file not found: ${input}`
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// Build Tailwind CLI arguments
|
|
76
|
+
const args = [
|
|
77
|
+
'@tailwindcss/cli',
|
|
78
|
+
'-i', input
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
// For in-memory compilation, use stdout
|
|
82
|
+
const useStdout = output === ':memory:'
|
|
83
|
+
if (!useStdout) {
|
|
84
|
+
args.push('-o', output)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (minify) {
|
|
88
|
+
args.push('--minify')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Execute Tailwind CLI synchronously
|
|
92
|
+
const result = spawnSync('bunx', args, {
|
|
93
|
+
cwd: path.dirname(input),
|
|
94
|
+
encoding: 'utf-8',
|
|
95
|
+
stdio: useStdout ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'inherit', 'pipe'],
|
|
96
|
+
env: { ...process.env }
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const duration = Math.round(performance.now() - startTime)
|
|
100
|
+
|
|
101
|
+
if (result.status !== 0) {
|
|
102
|
+
const errorMsg = result.stderr?.toString() || 'Unknown compilation error'
|
|
103
|
+
return {
|
|
104
|
+
css: '',
|
|
105
|
+
duration,
|
|
106
|
+
success: false,
|
|
107
|
+
error: `Tailwind compilation failed: ${errorMsg}`
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Get CSS content
|
|
112
|
+
let css = ''
|
|
113
|
+
if (useStdout) {
|
|
114
|
+
css = result.stdout?.toString() || ''
|
|
115
|
+
} else if (fs.existsSync(output)) {
|
|
116
|
+
css = fs.readFileSync(output, 'utf-8')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
css,
|
|
121
|
+
duration,
|
|
122
|
+
success: true
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
} catch (error: any) {
|
|
126
|
+
return {
|
|
127
|
+
css: '',
|
|
128
|
+
duration: Math.round(performance.now() - startTime),
|
|
129
|
+
success: false,
|
|
130
|
+
error: error.message
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Compile CSS asynchronously (non-blocking)
|
|
137
|
+
*
|
|
138
|
+
* Used for HMR updates where we don't want to block the main thread.
|
|
139
|
+
*/
|
|
140
|
+
export async function compileCssAsync(options: CSSCompileOptions): Promise<CSSCompileResult> {
|
|
141
|
+
return new Promise((resolve) => {
|
|
142
|
+
const startTime = performance.now()
|
|
143
|
+
const { input, output, minify = false } = options
|
|
144
|
+
|
|
145
|
+
if (!fs.existsSync(input)) {
|
|
146
|
+
resolve({
|
|
147
|
+
css: '',
|
|
148
|
+
duration: 0,
|
|
149
|
+
success: false,
|
|
150
|
+
error: `CSS input file not found: ${input}`
|
|
151
|
+
})
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const args = ['@tailwindcss/cli', '-i', input]
|
|
156
|
+
const useStdout = output === ':memory:'
|
|
157
|
+
|
|
158
|
+
if (!useStdout) {
|
|
159
|
+
args.push('-o', output)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (minify) {
|
|
163
|
+
args.push('--minify')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const child = spawn('bunx', args, {
|
|
167
|
+
cwd: path.dirname(input),
|
|
168
|
+
stdio: useStdout ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'inherit', 'pipe'],
|
|
169
|
+
env: { ...process.env }
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
let stdout = ''
|
|
173
|
+
let stderr = ''
|
|
174
|
+
|
|
175
|
+
if (useStdout && child.stdout) {
|
|
176
|
+
child.stdout.on('data', (data) => { stdout += data.toString() })
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (child.stderr) {
|
|
180
|
+
child.stderr.on('data', (data) => { stderr += data.toString() })
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
child.on('close', (code) => {
|
|
184
|
+
const duration = Math.round(performance.now() - startTime)
|
|
185
|
+
|
|
186
|
+
if (code !== 0) {
|
|
187
|
+
resolve({
|
|
188
|
+
css: '',
|
|
189
|
+
duration,
|
|
190
|
+
success: false,
|
|
191
|
+
error: `Tailwind compilation failed: ${stderr}`
|
|
192
|
+
})
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let css = ''
|
|
197
|
+
if (useStdout) {
|
|
198
|
+
css = stdout
|
|
199
|
+
} else if (fs.existsSync(output)) {
|
|
200
|
+
css = fs.readFileSync(output, 'utf-8')
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
resolve({
|
|
204
|
+
css,
|
|
205
|
+
duration,
|
|
206
|
+
success: true
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
child.on('error', (err) => {
|
|
211
|
+
resolve({
|
|
212
|
+
css: '',
|
|
213
|
+
duration: Math.round(performance.now() - startTime),
|
|
214
|
+
success: false,
|
|
215
|
+
error: err.message
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ============================================
|
|
222
|
+
// CSS Watcher for HMR
|
|
223
|
+
// ============================================
|
|
224
|
+
|
|
225
|
+
export interface CSSWatchOptions extends CSSCompileOptions {
|
|
226
|
+
/** Callback when CSS changes */
|
|
227
|
+
onChange: (result: CSSCompileResult) => void
|
|
228
|
+
/** Debounce delay in ms */
|
|
229
|
+
debounce?: number
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Watch CSS and source files for changes, recompile on change
|
|
234
|
+
*
|
|
235
|
+
* This is used by the dev server for HMR support.
|
|
236
|
+
* It watches both the CSS entry point AND all .zen files
|
|
237
|
+
* that Tailwind scans for class names.
|
|
238
|
+
*/
|
|
239
|
+
export function watchCss(options: CSSWatchOptions): () => void {
|
|
240
|
+
const { input, output, minify, onChange, debounce = 100 } = options
|
|
241
|
+
|
|
242
|
+
let timeout: NodeJS.Timeout | null = null
|
|
243
|
+
let isCompiling = false
|
|
244
|
+
|
|
245
|
+
const recompile = async () => {
|
|
246
|
+
if (isCompiling) return
|
|
247
|
+
isCompiling = true
|
|
248
|
+
|
|
249
|
+
const result = await compileCssAsync({ input, output, minify })
|
|
250
|
+
onChange(result)
|
|
251
|
+
|
|
252
|
+
isCompiling = false
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const debouncedRecompile = () => {
|
|
256
|
+
if (timeout) clearTimeout(timeout)
|
|
257
|
+
timeout = setTimeout(recompile, debounce)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Watch the styles directory
|
|
261
|
+
const stylesDir = path.dirname(input)
|
|
262
|
+
const stylesWatcher = fs.watch(stylesDir, { recursive: true }, (event, filename) => {
|
|
263
|
+
if (filename?.endsWith('.css')) {
|
|
264
|
+
debouncedRecompile()
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
// Watch source files that Tailwind scans (for class changes)
|
|
269
|
+
// This assumes standard Zenith structure: src/pages, src/components, src/layouts
|
|
270
|
+
const srcDir = path.resolve(stylesDir, '..')
|
|
271
|
+
let srcWatcher: fs.FSWatcher | null = null
|
|
272
|
+
|
|
273
|
+
if (fs.existsSync(srcDir)) {
|
|
274
|
+
srcWatcher = fs.watch(srcDir, { recursive: true }, (event, filename) => {
|
|
275
|
+
if (filename?.endsWith('.zen') || filename?.endsWith('.tsx') || filename?.endsWith('.jsx')) {
|
|
276
|
+
debouncedRecompile()
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Return cleanup function
|
|
282
|
+
return () => {
|
|
283
|
+
if (timeout) clearTimeout(timeout)
|
|
284
|
+
stylesWatcher.close()
|
|
285
|
+
srcWatcher?.close()
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ============================================
|
|
290
|
+
// Path Utilities
|
|
291
|
+
// ============================================
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Resolve the canonical globals.css path for a Zenith project
|
|
295
|
+
*/
|
|
296
|
+
export function resolveGlobalsCss(projectRoot: string): string | null {
|
|
297
|
+
// Check for globals.css (canonical)
|
|
298
|
+
const globalsPath = path.join(projectRoot, 'src', 'styles', 'globals.css')
|
|
299
|
+
if (fs.existsSync(globalsPath)) return globalsPath
|
|
300
|
+
|
|
301
|
+
// Check for global.css (legacy)
|
|
302
|
+
const globalPath = path.join(projectRoot, 'src', 'styles', 'global.css')
|
|
303
|
+
if (fs.existsSync(globalPath)) return globalPath
|
|
304
|
+
|
|
305
|
+
return null
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get the output path for compiled CSS
|
|
310
|
+
*/
|
|
311
|
+
export function getCompiledCssPath(projectRoot: string, mode: 'dev' | 'build'): string {
|
|
312
|
+
if (mode === 'build') {
|
|
313
|
+
return path.join(projectRoot, 'dist', 'assets', 'styles.css')
|
|
314
|
+
}
|
|
315
|
+
// In dev mode, we use in-memory compilation
|
|
316
|
+
return ':memory:'
|
|
317
|
+
}
|
package/compiler/ssg-build.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { discoverPages, generateRouteDefinition } from "../router/manifest"
|
|
|
20
20
|
import { analyzePageSource, getAnalysisSummary, getBuildOutputType, type PageAnalysis } from "./build-analyzer"
|
|
21
21
|
import { generateBundleJS } from "../runtime/bundle-generator"
|
|
22
22
|
import { loadContent } from "../cli/utils/content"
|
|
23
|
+
import { compileCss, resolveGlobalsCss } from "./css"
|
|
23
24
|
|
|
24
25
|
// ============================================
|
|
25
26
|
// Types
|
|
@@ -292,12 +293,23 @@ export function buildSSG(options: SSGBuildOptions): void {
|
|
|
292
293
|
|
|
293
294
|
console.log('')
|
|
294
295
|
|
|
295
|
-
//
|
|
296
|
+
// Compile global styles (Tailwind CSS)
|
|
296
297
|
let globalStyles = ''
|
|
297
|
-
const
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
|
|
298
|
+
const globalsCssPath = resolveGlobalsCss(baseDir)
|
|
299
|
+
if (globalsCssPath) {
|
|
300
|
+
console.log('📦 Compiling CSS:', path.relative(baseDir, globalsCssPath))
|
|
301
|
+
const cssOutputPath = path.join(outDir, 'assets', 'styles.css')
|
|
302
|
+
const result = compileCss({
|
|
303
|
+
input: globalsCssPath,
|
|
304
|
+
output: cssOutputPath,
|
|
305
|
+
minify: true
|
|
306
|
+
})
|
|
307
|
+
if (result.success) {
|
|
308
|
+
globalStyles = result.css
|
|
309
|
+
console.log(`📦 Generated assets/styles.css (${result.duration}ms)`)
|
|
310
|
+
} else {
|
|
311
|
+
console.error('❌ CSS compilation failed:', result.error)
|
|
312
|
+
}
|
|
301
313
|
}
|
|
302
314
|
|
|
303
315
|
// Write bundle.js if any pages need hydration
|
|
@@ -307,12 +319,6 @@ export function buildSSG(options: SSGBuildOptions): void {
|
|
|
307
319
|
console.log('📦 Generated assets/bundle.js')
|
|
308
320
|
}
|
|
309
321
|
|
|
310
|
-
// Write global styles
|
|
311
|
-
if (globalStyles) {
|
|
312
|
-
fs.writeFileSync(path.join(outDir, 'assets', 'styles.css'), globalStyles)
|
|
313
|
-
console.log('📦 Generated assets/styles.css')
|
|
314
|
-
}
|
|
315
|
-
|
|
316
322
|
// Write each page
|
|
317
323
|
for (const page of compiledPages) {
|
|
318
324
|
// Create output directory
|