@zenithbuild/core 0.5.0 → 0.6.1
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 +7 -6
- package/compiler/parse/importTypes.ts +78 -0
- package/compiler/parse/parseImports.ts +309 -0
- package/compiler/runtime/transformIR.ts +2 -2
- package/compiler/transform/componentScriptTransformer.ts +111 -91
- package/dist/cli.js +2 -0
- package/dist/zen-build.js +5665 -305
- package/dist/zen-dev.js +5665 -305
- package/dist/zen-preview.js +5665 -305
- package/dist/zenith.js +5665 -305
- package/package.json +3 -1
|
@@ -5,24 +5,18 @@
|
|
|
5
5
|
* Uses namespace binding pattern for cleaner output:
|
|
6
6
|
* const { signal, effect, onMount, ... } = __inst;
|
|
7
7
|
*
|
|
8
|
-
* Uses
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
8
|
+
* Uses Acorn AST parser for deterministic import parsing.
|
|
9
|
+
* Phase 1: Analysis only - imports are parsed and categorized.
|
|
10
|
+
* Phase 2 (bundling) happens in dev.ts.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* Import handling:
|
|
13
|
+
* - .zen imports: Stripped (compile-time resolved)
|
|
14
|
+
* - npm imports: Stored as structured metadata for later bundling
|
|
13
15
|
*/
|
|
14
16
|
|
|
15
|
-
import { init, parse } from 'es-module-lexer'
|
|
16
17
|
import type { ComponentScriptIR, ScriptImport } from '../ir/types'
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
let lexerInitialized = false
|
|
20
|
-
async function ensureLexerInit(): Promise<void> {
|
|
21
|
-
if (!lexerInitialized) {
|
|
22
|
-
await init
|
|
23
|
-
lexerInitialized = true
|
|
24
|
-
}
|
|
25
|
-
}
|
|
18
|
+
import { parseImports, categorizeImports } from '../parse/parseImports'
|
|
19
|
+
import type { ParsedImport } from '../parse/importTypes'
|
|
26
20
|
|
|
27
21
|
/**
|
|
28
22
|
* Namespace bindings - destructured from the instance
|
|
@@ -58,74 +52,100 @@ export interface TransformResult {
|
|
|
58
52
|
}
|
|
59
53
|
|
|
60
54
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* @param scriptContent - Raw script content
|
|
64
|
-
* @returns Object with imports array and script with imports stripped
|
|
55
|
+
* Convert ParsedImport to ScriptImport for compatibility with existing IR
|
|
65
56
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
57
|
+
function toScriptImport(parsed: ParsedImport): ScriptImport {
|
|
58
|
+
// Build specifiers string from parsed specifiers
|
|
59
|
+
let specifiers = ''
|
|
60
|
+
|
|
61
|
+
if (parsed.kind === 'default') {
|
|
62
|
+
specifiers = parsed.specifiers[0]?.local || ''
|
|
63
|
+
} else if (parsed.kind === 'namespace') {
|
|
64
|
+
specifiers = `* as ${parsed.specifiers[0]?.local || ''}`
|
|
65
|
+
} else if (parsed.kind === 'named') {
|
|
66
|
+
const parts = parsed.specifiers.map(s =>
|
|
67
|
+
s.imported ? `${s.imported} as ${s.local}` : s.local
|
|
68
|
+
)
|
|
69
|
+
specifiers = `{ ${parts.join(', ')} }`
|
|
70
|
+
} else if (parsed.kind === 'side-effect') {
|
|
71
|
+
specifiers = ''
|
|
72
|
+
}
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
return {
|
|
75
|
+
source: parsed.source,
|
|
76
|
+
specifiers,
|
|
77
|
+
typeOnly: parsed.isTypeOnly,
|
|
78
|
+
sideEffect: parsed.kind === 'side-effect'
|
|
79
|
+
}
|
|
80
|
+
}
|
|
77
81
|
|
|
78
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Strip imports from source code based on parsed import locations
|
|
84
|
+
*
|
|
85
|
+
* @param source - Original source code
|
|
86
|
+
* @param imports - Parsed imports to strip
|
|
87
|
+
* @returns Source with imports removed
|
|
88
|
+
*/
|
|
89
|
+
function stripImportsFromSource(source: string, imports: ParsedImport[]): string {
|
|
90
|
+
if (imports.length === 0) return source
|
|
79
91
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const importStatement = scriptContent.slice(imp.ss, imp.se)
|
|
92
|
+
// Sort by start position descending for safe removal
|
|
93
|
+
const sorted = [...imports].sort((a, b) => b.location.start - a.location.start)
|
|
83
94
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
95
|
+
let result = source
|
|
96
|
+
for (const imp of sorted) {
|
|
97
|
+
// Remove the import statement
|
|
98
|
+
const before = result.slice(0, imp.location.start)
|
|
99
|
+
const after = result.slice(imp.location.end)
|
|
89
100
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
101
|
+
// Also remove trailing newline if present
|
|
102
|
+
const trimmedAfter = after.startsWith('\n') ? after.slice(1) : after
|
|
103
|
+
result = before + trimmedAfter
|
|
104
|
+
}
|
|
95
105
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const isSideEffect = imp.ss === imp.se || !importStatement.includes(' from ')
|
|
99
|
-
|
|
100
|
-
// Extract specifiers from the import statement
|
|
101
|
-
let specifiers = ''
|
|
102
|
-
if (!isSideEffect) {
|
|
103
|
-
const fromIndex = importStatement.indexOf(' from ')
|
|
104
|
-
if (fromIndex !== -1) {
|
|
105
|
-
// Get everything between 'import' (or 'import type') and 'from'
|
|
106
|
-
const start = isTypeOnly ? 'import type '.length : 'import '.length
|
|
107
|
-
specifiers = importStatement.slice(start, fromIndex).trim()
|
|
108
|
-
}
|
|
109
|
-
}
|
|
106
|
+
return result
|
|
107
|
+
}
|
|
110
108
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Parse and extract imports from script content using Acorn AST parser
|
|
111
|
+
*
|
|
112
|
+
* Phase 1: Deterministic parsing - no bundling or resolution
|
|
113
|
+
*
|
|
114
|
+
* @param scriptContent - Raw script content
|
|
115
|
+
* @param componentName - Name of the component (for error context)
|
|
116
|
+
* @returns Object with npm imports array and script with all imports stripped
|
|
117
|
+
*/
|
|
118
|
+
export function parseAndExtractImports(
|
|
119
|
+
scriptContent: string,
|
|
120
|
+
componentName: string = 'unknown'
|
|
121
|
+
): {
|
|
122
|
+
imports: ScriptImport[]
|
|
123
|
+
strippedCode: string
|
|
124
|
+
} {
|
|
125
|
+
// Parse imports using Acorn AST
|
|
126
|
+
const parseResult = parseImports(scriptContent, componentName)
|
|
117
127
|
|
|
118
|
-
|
|
119
|
-
|
|
128
|
+
if (!parseResult.success) {
|
|
129
|
+
console.warn(`[Zenith] Import parse warnings for ${componentName}:`, parseResult.errors)
|
|
120
130
|
}
|
|
121
131
|
|
|
122
|
-
//
|
|
123
|
-
|
|
132
|
+
// Categorize imports
|
|
133
|
+
const { zenImports, npmImports, relativeImports } = categorizeImports(parseResult.imports)
|
|
134
|
+
|
|
135
|
+
// Convert npm imports to ScriptImport format
|
|
136
|
+
const scriptImports = npmImports.map(toScriptImport)
|
|
124
137
|
|
|
125
|
-
//
|
|
126
|
-
imports
|
|
138
|
+
// Strip ALL imports from source (zen, npm, and relative)
|
|
139
|
+
// - .zen imports: resolved at compile time
|
|
140
|
+
// - npm imports: will be bundled separately
|
|
141
|
+
// - relative imports: resolved at compile time
|
|
142
|
+
const allImportsToStrip = [...zenImports, ...npmImports, ...relativeImports]
|
|
143
|
+
const strippedCode = stripImportsFromSource(scriptContent, allImportsToStrip)
|
|
127
144
|
|
|
128
|
-
return {
|
|
145
|
+
return {
|
|
146
|
+
imports: scriptImports,
|
|
147
|
+
strippedCode
|
|
148
|
+
}
|
|
129
149
|
}
|
|
130
150
|
|
|
131
151
|
/**
|
|
@@ -136,13 +156,13 @@ export async function parseAndExtractImports(scriptContent: string): Promise<{
|
|
|
136
156
|
* @param props - Declared prop names
|
|
137
157
|
* @returns TransformResult with transformed script and extracted imports
|
|
138
158
|
*/
|
|
139
|
-
export
|
|
159
|
+
export function transformComponentScript(
|
|
140
160
|
componentName: string,
|
|
141
161
|
scriptContent: string,
|
|
142
162
|
props: string[]
|
|
143
|
-
):
|
|
144
|
-
// Parse and extract imports using
|
|
145
|
-
const { imports, strippedCode } =
|
|
163
|
+
): TransformResult {
|
|
164
|
+
// Parse and extract imports using Acorn AST
|
|
165
|
+
const { imports, strippedCode } = parseAndExtractImports(scriptContent, componentName)
|
|
146
166
|
|
|
147
167
|
let transformed = strippedCode
|
|
148
168
|
|
|
@@ -247,34 +267,34 @@ export function emitImports(imports: ScriptImport[]): string {
|
|
|
247
267
|
/**
|
|
248
268
|
* Transform all component scripts from collected ComponentScriptIR
|
|
249
269
|
*
|
|
270
|
+
* Now synchronous since Acorn parsing is synchronous.
|
|
271
|
+
*
|
|
250
272
|
* @param componentScripts - Array of component script IRs
|
|
251
273
|
* @returns TransformAllResult with combined code and deduplicated imports
|
|
252
274
|
*/
|
|
253
|
-
export
|
|
275
|
+
export function transformAllComponentScripts(
|
|
254
276
|
componentScripts: ComponentScriptIR[]
|
|
255
|
-
):
|
|
277
|
+
): TransformAllResult {
|
|
256
278
|
if (!componentScripts || componentScripts.length === 0) {
|
|
257
279
|
return { code: '', imports: [] }
|
|
258
280
|
}
|
|
259
281
|
|
|
260
282
|
const allImports: ScriptImport[] = []
|
|
261
283
|
|
|
262
|
-
const factories =
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
})
|
|
277
|
-
)
|
|
284
|
+
const factories = componentScripts
|
|
285
|
+
.filter(comp => comp.script && comp.script.trim().length > 0)
|
|
286
|
+
.map(comp => {
|
|
287
|
+
const result = transformComponentScript(
|
|
288
|
+
comp.name,
|
|
289
|
+
comp.script,
|
|
290
|
+
comp.props
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
// Collect imports
|
|
294
|
+
allImports.push(...result.imports)
|
|
295
|
+
|
|
296
|
+
return generateComponentFactory(comp.name, result.script, comp.props)
|
|
297
|
+
})
|
|
278
298
|
|
|
279
299
|
return {
|
|
280
300
|
code: factories.join('\n'),
|