dalila 1.1.0 → 1.1.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/dist/compiler/dalila-lang.d.ts +85 -0
- package/dist/compiler/dalila-lang.js +442 -0
- package/dist/context/context.d.ts +7 -0
- package/dist/context/context.js +61 -0
- package/dist/context/index.d.ts +1 -0
- package/dist/context/index.js +1 -0
- package/dist/core/dev.d.ts +23 -0
- package/dist/core/dev.js +157 -0
- package/dist/core/for.d.ts +3 -0
- package/dist/core/for.js +157 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +13 -0
- package/dist/core/key.d.ts +33 -0
- package/dist/core/key.js +83 -0
- package/dist/core/match.d.ts +22 -0
- package/dist/core/match.js +169 -0
- package/dist/core/mutation.d.ts +55 -0
- package/dist/core/mutation.js +128 -0
- package/dist/core/query.d.ts +72 -0
- package/dist/core/query.js +151 -0
- package/dist/core/resource.d.ts +189 -0
- package/dist/core/resource.js +678 -0
- package/dist/core/scheduler.d.ts +80 -0
- package/dist/core/scheduler.js +227 -0
- package/dist/core/scope.d.ts +38 -0
- package/dist/core/scope.js +54 -0
- package/dist/core/signal.d.ts +66 -0
- package/dist/core/signal.js +365 -0
- package/dist/core/virtual.d.ts +26 -0
- package/dist/core/virtual.js +277 -0
- package/dist/core/watch.d.ts +19 -0
- package/dist/core/watch.js +277 -0
- package/dist/core/when.d.ts +23 -0
- package/dist/core/when.js +118 -0
- package/dist/dom/elements.d.ts +15 -0
- package/dist/dom/elements.js +100 -0
- package/dist/dom/events.d.ts +7 -0
- package/dist/dom/events.js +47 -0
- package/dist/dom/index.d.ts +2 -0
- package/dist/dom/index.js +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +8 -0
- package/dist/router/index.d.ts +2 -0
- package/dist/router/index.js +2 -0
- package/dist/router/route.d.ts +23 -0
- package/dist/router/route.js +48 -0
- package/dist/router/router.d.ts +23 -0
- package/dist/router/router.js +169 -0
- package/package.json +1 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export interface DalilaTemplate {
|
|
2
|
+
path: string;
|
|
3
|
+
content: string;
|
|
4
|
+
componentName: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CompilationResult {
|
|
7
|
+
source: string;
|
|
8
|
+
typeDefinition: string;
|
|
9
|
+
diagnostics: Diagnostic[];
|
|
10
|
+
}
|
|
11
|
+
export interface Diagnostic {
|
|
12
|
+
message: string;
|
|
13
|
+
line: number;
|
|
14
|
+
column: number;
|
|
15
|
+
severity: 'error' | 'warning' | 'info';
|
|
16
|
+
}
|
|
17
|
+
export interface ElementNode {
|
|
18
|
+
type: 'element';
|
|
19
|
+
tagName: string;
|
|
20
|
+
attributes: Attribute[];
|
|
21
|
+
children: Node[];
|
|
22
|
+
selfClosing: boolean;
|
|
23
|
+
location: Location;
|
|
24
|
+
}
|
|
25
|
+
export interface TextNode {
|
|
26
|
+
type: 'text';
|
|
27
|
+
content: string;
|
|
28
|
+
location: Location;
|
|
29
|
+
}
|
|
30
|
+
export interface InterpolationNode {
|
|
31
|
+
type: 'interpolation';
|
|
32
|
+
identifier: string;
|
|
33
|
+
location: Location;
|
|
34
|
+
}
|
|
35
|
+
export interface Attribute {
|
|
36
|
+
name: string;
|
|
37
|
+
value: string | InterpolationNode | null;
|
|
38
|
+
location: Location;
|
|
39
|
+
}
|
|
40
|
+
export interface Location {
|
|
41
|
+
line: number;
|
|
42
|
+
column: number;
|
|
43
|
+
offset: number;
|
|
44
|
+
}
|
|
45
|
+
export type Node = ElementNode | TextNode | InterpolationNode;
|
|
46
|
+
export declare function isElementNode(node: Node): node is ElementNode;
|
|
47
|
+
export declare function isTextNode(node: Node): node is TextNode;
|
|
48
|
+
export declare function isInterpolationNode(node: Node): node is InterpolationNode;
|
|
49
|
+
export declare class DalilaParser {
|
|
50
|
+
private input;
|
|
51
|
+
private position;
|
|
52
|
+
private line;
|
|
53
|
+
private column;
|
|
54
|
+
private rawTextTags;
|
|
55
|
+
constructor(input: string);
|
|
56
|
+
parse(): {
|
|
57
|
+
ast: Node[];
|
|
58
|
+
diagnostics: Diagnostic[];
|
|
59
|
+
};
|
|
60
|
+
private parseElement;
|
|
61
|
+
private parseTagName;
|
|
62
|
+
private parseAttributes;
|
|
63
|
+
private parseAttribute;
|
|
64
|
+
private parseInterpolation;
|
|
65
|
+
private parseText;
|
|
66
|
+
private skipWhitespace;
|
|
67
|
+
private match;
|
|
68
|
+
private lookingAt;
|
|
69
|
+
private peek;
|
|
70
|
+
private advance;
|
|
71
|
+
private backup;
|
|
72
|
+
private isAtEnd;
|
|
73
|
+
private currentLocation;
|
|
74
|
+
}
|
|
75
|
+
export declare class DalilaCodeGenerator {
|
|
76
|
+
private indentLevel;
|
|
77
|
+
private output;
|
|
78
|
+
generate(ast: Node[], componentName: string): CompilationResult;
|
|
79
|
+
private generateElement;
|
|
80
|
+
private generateAttribute;
|
|
81
|
+
private generateTextInterpolation;
|
|
82
|
+
private generateTypeDefinition;
|
|
83
|
+
private emit;
|
|
84
|
+
}
|
|
85
|
+
export declare function compileDalilaTemplate(template: DalilaTemplate): CompilationResult;
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
// dalila Template Language Compiler v0.1
|
|
2
|
+
// SPEC Implementation
|
|
3
|
+
export function isElementNode(node) {
|
|
4
|
+
return node.type === 'element';
|
|
5
|
+
}
|
|
6
|
+
export function isTextNode(node) {
|
|
7
|
+
return node.type === 'text';
|
|
8
|
+
}
|
|
9
|
+
export function isInterpolationNode(node) {
|
|
10
|
+
return node.type === 'interpolation';
|
|
11
|
+
}
|
|
12
|
+
// Parser
|
|
13
|
+
export class DalilaParser {
|
|
14
|
+
constructor(input) {
|
|
15
|
+
this.position = 0;
|
|
16
|
+
this.line = 1;
|
|
17
|
+
this.column = 1;
|
|
18
|
+
this.rawTextTags = new Set(['pre', 'code']);
|
|
19
|
+
this.input = input;
|
|
20
|
+
}
|
|
21
|
+
parse() {
|
|
22
|
+
const diagnostics = [];
|
|
23
|
+
const ast = [];
|
|
24
|
+
try {
|
|
25
|
+
while (!this.isAtEnd()) {
|
|
26
|
+
this.skipWhitespace();
|
|
27
|
+
if (this.isAtEnd())
|
|
28
|
+
break;
|
|
29
|
+
if (this.peek() === '<') {
|
|
30
|
+
const element = this.parseElement();
|
|
31
|
+
if (element) {
|
|
32
|
+
ast.push(element);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Parse text content
|
|
37
|
+
const text = this.parseText(true);
|
|
38
|
+
if (text) {
|
|
39
|
+
if (isTextNode(text)) {
|
|
40
|
+
ast.push(text);
|
|
41
|
+
}
|
|
42
|
+
else if (isInterpolationNode(text)) {
|
|
43
|
+
ast.push(text);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
51
|
+
diagnostics.push({
|
|
52
|
+
message: `Parse error: ${errorMessage}`,
|
|
53
|
+
line: this.line,
|
|
54
|
+
column: this.column,
|
|
55
|
+
severity: 'error'
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return { ast, diagnostics };
|
|
59
|
+
}
|
|
60
|
+
parseElement() {
|
|
61
|
+
if (!this.match('<'))
|
|
62
|
+
return null;
|
|
63
|
+
const startLocation = this.currentLocation();
|
|
64
|
+
// Parse tag name
|
|
65
|
+
const tagName = this.parseTagName();
|
|
66
|
+
if (!tagName) {
|
|
67
|
+
throw new Error('Expected tag name after <');
|
|
68
|
+
}
|
|
69
|
+
// Parse attributes
|
|
70
|
+
const attributes = this.parseAttributes();
|
|
71
|
+
// Check if self-closing
|
|
72
|
+
const selfClosing = this.match('/>');
|
|
73
|
+
let children = [];
|
|
74
|
+
if (!selfClosing) {
|
|
75
|
+
if (!this.match('>')) {
|
|
76
|
+
throw new Error('Expected > after tag attributes');
|
|
77
|
+
}
|
|
78
|
+
// Parse children until closing tag
|
|
79
|
+
children = [];
|
|
80
|
+
const allowInterpolation = !this.rawTextTags.has(tagName);
|
|
81
|
+
while (!this.isAtEnd() && !this.lookingAt(`</${tagName}`)) {
|
|
82
|
+
this.skipWhitespace();
|
|
83
|
+
if (this.lookingAt(`</${tagName}`))
|
|
84
|
+
break;
|
|
85
|
+
if (this.peek() === '<') {
|
|
86
|
+
const childElement = this.parseElement();
|
|
87
|
+
if (childElement) {
|
|
88
|
+
children.push(childElement);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const text = this.parseText(allowInterpolation);
|
|
93
|
+
if (text) {
|
|
94
|
+
children.push(text);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Parse closing tag
|
|
99
|
+
if (!this.match(`</${tagName}>`)) {
|
|
100
|
+
throw new Error(`Expected closing tag </${tagName}>`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
type: 'element',
|
|
105
|
+
tagName,
|
|
106
|
+
attributes,
|
|
107
|
+
children,
|
|
108
|
+
selfClosing,
|
|
109
|
+
location: startLocation
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
parseTagName() {
|
|
113
|
+
let name = '';
|
|
114
|
+
while (!this.isAtEnd() && /[a-zA-Z0-9\-_]/.test(this.peek())) {
|
|
115
|
+
name += this.advance();
|
|
116
|
+
}
|
|
117
|
+
return name || null;
|
|
118
|
+
}
|
|
119
|
+
parseAttributes() {
|
|
120
|
+
const attributes = [];
|
|
121
|
+
while (!this.isAtEnd() && !['>', '/>'].includes(this.peek(2))) {
|
|
122
|
+
this.skipWhitespace();
|
|
123
|
+
if (['>', '/>'].includes(this.peek(2)))
|
|
124
|
+
break;
|
|
125
|
+
const attr = this.parseAttribute();
|
|
126
|
+
if (attr) {
|
|
127
|
+
attributes.push(attr);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return attributes;
|
|
131
|
+
}
|
|
132
|
+
parseAttribute() {
|
|
133
|
+
const startLocation = this.currentLocation();
|
|
134
|
+
// Parse attribute name
|
|
135
|
+
let name = '';
|
|
136
|
+
while (!this.isAtEnd() && /[a-zA-Z0-9\-_:]/.test(this.peek())) {
|
|
137
|
+
name += this.advance();
|
|
138
|
+
}
|
|
139
|
+
if (!name)
|
|
140
|
+
return null;
|
|
141
|
+
this.skipWhitespace();
|
|
142
|
+
let value = null;
|
|
143
|
+
if (this.match('=')) {
|
|
144
|
+
this.skipWhitespace();
|
|
145
|
+
if (this.match('"')) {
|
|
146
|
+
// Parse string literal or interpolation
|
|
147
|
+
if (this.peek() === '{') {
|
|
148
|
+
const interpolation = this.parseInterpolation();
|
|
149
|
+
if (interpolation) {
|
|
150
|
+
value = interpolation;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// Parse string literal
|
|
155
|
+
let str = '';
|
|
156
|
+
while (!this.isAtEnd() && this.peek() !== '"') {
|
|
157
|
+
str += this.advance();
|
|
158
|
+
}
|
|
159
|
+
value = str;
|
|
160
|
+
}
|
|
161
|
+
this.match('"'); // consume closing quote
|
|
162
|
+
}
|
|
163
|
+
else if (this.peek() === '{') {
|
|
164
|
+
value = this.parseInterpolation();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
name,
|
|
169
|
+
value,
|
|
170
|
+
location: startLocation
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
parseInterpolation() {
|
|
174
|
+
if (!this.match('{'))
|
|
175
|
+
return null;
|
|
176
|
+
const startLocation = this.currentLocation();
|
|
177
|
+
this.skipWhitespace();
|
|
178
|
+
// Parse identifier
|
|
179
|
+
let identifier = '';
|
|
180
|
+
while (!this.isAtEnd() && /[a-zA-Z_$][a-zA-Z0-9_$]*/.test(this.peek())) {
|
|
181
|
+
identifier += this.advance();
|
|
182
|
+
}
|
|
183
|
+
this.skipWhitespace();
|
|
184
|
+
if (!this.match('}')) {
|
|
185
|
+
throw new Error('Expected } to close interpolation');
|
|
186
|
+
}
|
|
187
|
+
if (!identifier) {
|
|
188
|
+
throw new Error('Expected identifier in interpolation');
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
type: 'interpolation',
|
|
192
|
+
identifier,
|
|
193
|
+
location: startLocation
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
parseText(allowInterpolation) {
|
|
197
|
+
const startLocation = this.currentLocation();
|
|
198
|
+
let content = '';
|
|
199
|
+
while (!this.isAtEnd() && this.peek() !== '<') {
|
|
200
|
+
if (allowInterpolation && this.peek() === '{') {
|
|
201
|
+
// Found interpolation in text
|
|
202
|
+
if (content) {
|
|
203
|
+
// Return text node up to interpolation
|
|
204
|
+
this.backup(content.length);
|
|
205
|
+
return {
|
|
206
|
+
type: 'text',
|
|
207
|
+
content,
|
|
208
|
+
location: startLocation
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
// Parse interpolation
|
|
212
|
+
const interpolation = this.parseInterpolation();
|
|
213
|
+
if (interpolation) {
|
|
214
|
+
return interpolation;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
content += this.advance();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (!content.trim())
|
|
222
|
+
return null;
|
|
223
|
+
return {
|
|
224
|
+
type: 'text',
|
|
225
|
+
content,
|
|
226
|
+
location: startLocation
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
skipWhitespace() {
|
|
230
|
+
while (!this.isAtEnd() && /\s/.test(this.peek())) {
|
|
231
|
+
if (this.peek() === '\n') {
|
|
232
|
+
this.line++;
|
|
233
|
+
this.column = 1;
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
this.column++;
|
|
237
|
+
}
|
|
238
|
+
this.position++;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
match(expected) {
|
|
242
|
+
if (this.lookingAt(expected)) {
|
|
243
|
+
for (let i = 0; i < expected.length; i++) {
|
|
244
|
+
if (expected[i] === '\n') {
|
|
245
|
+
this.line++;
|
|
246
|
+
this.column = 1;
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
this.column++;
|
|
250
|
+
}
|
|
251
|
+
this.position++;
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
lookingAt(expected) {
|
|
258
|
+
for (let i = 0; i < expected.length; i++) {
|
|
259
|
+
if (this.position + i >= this.input.length ||
|
|
260
|
+
this.input[this.position + i] !== expected[i]) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
peek(n = 1) {
|
|
267
|
+
const start = this.position;
|
|
268
|
+
return this.input.slice(start, start + n);
|
|
269
|
+
}
|
|
270
|
+
advance() {
|
|
271
|
+
if (this.isAtEnd())
|
|
272
|
+
return '\0';
|
|
273
|
+
const char = this.input[this.position++];
|
|
274
|
+
if (char === '\n') {
|
|
275
|
+
this.line++;
|
|
276
|
+
this.column = 1;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
this.column++;
|
|
280
|
+
}
|
|
281
|
+
return char;
|
|
282
|
+
}
|
|
283
|
+
backup(count) {
|
|
284
|
+
for (let i = 0; i < count; i++) {
|
|
285
|
+
this.position--;
|
|
286
|
+
const char = this.input[this.position];
|
|
287
|
+
if (char === '\n') {
|
|
288
|
+
this.line--;
|
|
289
|
+
// Note: column would need more complex tracking
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
this.column--;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
isAtEnd() {
|
|
297
|
+
return this.position >= this.input.length;
|
|
298
|
+
}
|
|
299
|
+
currentLocation() {
|
|
300
|
+
return {
|
|
301
|
+
line: this.line,
|
|
302
|
+
column: this.column,
|
|
303
|
+
offset: this.position
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Code Generator
|
|
308
|
+
export class DalilaCodeGenerator {
|
|
309
|
+
constructor() {
|
|
310
|
+
this.indentLevel = 0;
|
|
311
|
+
this.output = [];
|
|
312
|
+
}
|
|
313
|
+
generate(ast, componentName) {
|
|
314
|
+
this.output = [];
|
|
315
|
+
const diagnostics = [];
|
|
316
|
+
this.emit(`import { watch } from '../core/watch.js';`);
|
|
317
|
+
this.emit('');
|
|
318
|
+
this.emit(`export function render${componentName}(ctx: any): Node {`);
|
|
319
|
+
this.indentLevel++;
|
|
320
|
+
// Generate root fragment or single element
|
|
321
|
+
if (ast.length === 1 && ast[0].type === 'element') {
|
|
322
|
+
this.generateElement(ast[0], 'root');
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
this.emit('const root = document.createDocumentFragment();');
|
|
326
|
+
for (let i = 0; i < ast.length; i++) {
|
|
327
|
+
const node = ast[i];
|
|
328
|
+
const varName = `node_${i}`;
|
|
329
|
+
if (node.type === 'element') {
|
|
330
|
+
this.generateElement(node, varName);
|
|
331
|
+
}
|
|
332
|
+
else if (node.type === 'text') {
|
|
333
|
+
this.emit(`const ${varName} = document.createTextNode(${JSON.stringify(node.content)});`);
|
|
334
|
+
}
|
|
335
|
+
else if (node.type === 'interpolation') {
|
|
336
|
+
this.generateTextInterpolation(node, varName);
|
|
337
|
+
}
|
|
338
|
+
this.emit(`root.append(${varName});`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
this.emit('return root;');
|
|
342
|
+
this.indentLevel--;
|
|
343
|
+
this.emit('}');
|
|
344
|
+
// Generate type definition (simplified)
|
|
345
|
+
const typeDef = this.generateTypeDefinition(componentName, ast);
|
|
346
|
+
return {
|
|
347
|
+
source: this.output.join('\n'),
|
|
348
|
+
typeDefinition: typeDef,
|
|
349
|
+
diagnostics
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
generateElement(node, varName) {
|
|
353
|
+
this.emit(`const ${varName} = document.createElement(${JSON.stringify(node.tagName)});`);
|
|
354
|
+
// Generate attributes
|
|
355
|
+
for (const attr of node.attributes) {
|
|
356
|
+
this.generateAttribute(varName, attr);
|
|
357
|
+
}
|
|
358
|
+
// Generate children
|
|
359
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
360
|
+
const child = node.children[i];
|
|
361
|
+
const childVar = `${varName}_child_${i}`;
|
|
362
|
+
if (child.type === 'element') {
|
|
363
|
+
this.generateElement(child, childVar);
|
|
364
|
+
this.emit(`${varName}.append(${childVar});`);
|
|
365
|
+
}
|
|
366
|
+
else if (child.type === 'text') {
|
|
367
|
+
this.emit(`${varName}.append(${JSON.stringify(child.content)});`);
|
|
368
|
+
}
|
|
369
|
+
else if (child.type === 'interpolation') {
|
|
370
|
+
this.generateTextInterpolation(child, childVar);
|
|
371
|
+
this.emit(`${varName}.append(${childVar});`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
generateAttribute(elementVar, attr) {
|
|
376
|
+
const attrName = attr.name;
|
|
377
|
+
if (attr.value === null) {
|
|
378
|
+
// Boolean attribute
|
|
379
|
+
this.emit(`${elementVar}.setAttribute(${JSON.stringify(attrName)}, '');`);
|
|
380
|
+
}
|
|
381
|
+
else if (typeof attr.value === 'string') {
|
|
382
|
+
// String literal
|
|
383
|
+
this.emit(`${elementVar}.setAttribute(${JSON.stringify(attrName)}, ${JSON.stringify(attr.value)});`);
|
|
384
|
+
}
|
|
385
|
+
else if (attr.value.type === 'interpolation') {
|
|
386
|
+
// Dynamic attribute
|
|
387
|
+
if (attrName.startsWith('on:')) {
|
|
388
|
+
// Event handler
|
|
389
|
+
const eventName = attrName.slice(3); // remove 'on:'
|
|
390
|
+
this.emit(`${elementVar}.addEventListener(${JSON.stringify(eventName)}, ctx.${attr.value.identifier});`);
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
// Attribute binding
|
|
394
|
+
const watchFn = `() => {
|
|
395
|
+
const value = ctx.${attr.value.identifier}();
|
|
396
|
+
if (value == null || value === false) {
|
|
397
|
+
${elementVar}.removeAttribute(${JSON.stringify(attrName)});
|
|
398
|
+
} else {
|
|
399
|
+
${elementVar}.setAttribute(${JSON.stringify(attrName)}, String(value));
|
|
400
|
+
}
|
|
401
|
+
}`;
|
|
402
|
+
this.emit(`watch(${elementVar}, ${watchFn});`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
generateTextInterpolation(node, varName) {
|
|
407
|
+
this.emit(`const ${varName} = document.createTextNode('');`);
|
|
408
|
+
const watchFn = `() => {
|
|
409
|
+
${varName}.data = String(ctx.${node.identifier}() ?? '');
|
|
410
|
+
}`;
|
|
411
|
+
this.emit(`watch(${varName}, ${watchFn});`);
|
|
412
|
+
}
|
|
413
|
+
generateTypeDefinition(componentName, ast) {
|
|
414
|
+
// Simplified type generation - in a real implementation this would analyze all usages
|
|
415
|
+
return `export interface ${componentName}Ctx {
|
|
416
|
+
// Type definition would be inferred from template usage
|
|
417
|
+
[key: string]: any;
|
|
418
|
+
}`;
|
|
419
|
+
}
|
|
420
|
+
emit(line) {
|
|
421
|
+
const indent = ' '.repeat(this.indentLevel);
|
|
422
|
+
this.output.push(indent + line);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// Main Compiler
|
|
426
|
+
export function compileDalilaTemplate(template) {
|
|
427
|
+
const parser = new DalilaParser(template.content);
|
|
428
|
+
const { ast, diagnostics } = parser.parse();
|
|
429
|
+
if (diagnostics.some(d => d.severity === 'error')) {
|
|
430
|
+
return {
|
|
431
|
+
source: '',
|
|
432
|
+
typeDefinition: '',
|
|
433
|
+
diagnostics
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
const generator = new DalilaCodeGenerator();
|
|
437
|
+
const result = generator.generate(ast, template.componentName);
|
|
438
|
+
return {
|
|
439
|
+
...result,
|
|
440
|
+
diagnostics: [...diagnostics, ...result.diagnostics]
|
|
441
|
+
};
|
|
442
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface ContextToken<T> {
|
|
2
|
+
readonly name?: string;
|
|
3
|
+
readonly _brand: T;
|
|
4
|
+
}
|
|
5
|
+
export declare function createContext<T>(name?: string): ContextToken<T>;
|
|
6
|
+
export declare function provide<T>(token: ContextToken<T>, value: T): void;
|
|
7
|
+
export declare function inject<T>(token: ContextToken<T>): T;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getCurrentScope } from '../core/scope.js';
|
|
2
|
+
export function createContext(name) {
|
|
3
|
+
return { name };
|
|
4
|
+
}
|
|
5
|
+
class ContextRegistry {
|
|
6
|
+
constructor(parent) {
|
|
7
|
+
this.contexts = new Map();
|
|
8
|
+
this.parent = parent;
|
|
9
|
+
}
|
|
10
|
+
set(token, value) {
|
|
11
|
+
this.contexts.set(token, { token, value });
|
|
12
|
+
}
|
|
13
|
+
get(token) {
|
|
14
|
+
const value = this.contexts.get(token);
|
|
15
|
+
if (value !== undefined) {
|
|
16
|
+
return value.value;
|
|
17
|
+
}
|
|
18
|
+
if (this.parent) {
|
|
19
|
+
return this.parent.get(token);
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const scopeRegistries = new WeakMap();
|
|
25
|
+
function getScopeRegistry(scope) {
|
|
26
|
+
let registry = scopeRegistries.get(scope);
|
|
27
|
+
if (!registry) {
|
|
28
|
+
const parentScope = getCurrentScope();
|
|
29
|
+
const parentRegistry = parentScope ? scopeRegistries.get(parentScope) : undefined;
|
|
30
|
+
registry = new ContextRegistry(parentRegistry);
|
|
31
|
+
scopeRegistries.set(scope, registry);
|
|
32
|
+
}
|
|
33
|
+
return registry;
|
|
34
|
+
}
|
|
35
|
+
export function provide(token, value) {
|
|
36
|
+
const scope = getCurrentScope();
|
|
37
|
+
if (!scope) {
|
|
38
|
+
throw new Error('provide() must be called within a scope');
|
|
39
|
+
}
|
|
40
|
+
const registry = getScopeRegistry(scope);
|
|
41
|
+
registry.set(token, value);
|
|
42
|
+
}
|
|
43
|
+
export function inject(token) {
|
|
44
|
+
const scope = getCurrentScope();
|
|
45
|
+
if (!scope) {
|
|
46
|
+
throw new Error('inject() must be called within a scope');
|
|
47
|
+
}
|
|
48
|
+
let currentScope = scope;
|
|
49
|
+
while (currentScope) {
|
|
50
|
+
const registry = scopeRegistries.get(currentScope);
|
|
51
|
+
if (registry) {
|
|
52
|
+
const value = registry.get(token);
|
|
53
|
+
if (value !== undefined) {
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// For now, we don't have parent scope tracking, so we break
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`Context ${token.name || 'unnamed'} not found`);
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './context.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './context.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare function setDevMode(enabled: boolean): void;
|
|
2
|
+
export declare function isInDevMode(): boolean;
|
|
3
|
+
export declare function warnIfAsyncEffectWithoutAbort(): void;
|
|
4
|
+
export declare function warnIfLayoutReadInMutate(): void;
|
|
5
|
+
export declare function warnIfSignalUsedOutsideScope(): void;
|
|
6
|
+
export declare function checkLayoutThrashing(): Promise<void>;
|
|
7
|
+
export declare function monitorPerformance(): void;
|
|
8
|
+
export declare function detectMemoryLeaks(): void;
|
|
9
|
+
export declare function initDevTools(): Promise<void>;
|
|
10
|
+
export declare function inspectSignal<T>(signal: () => T): {
|
|
11
|
+
value: T;
|
|
12
|
+
subscribers: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function trackEffect(fn: Function): () => void;
|
|
15
|
+
export declare function getActiveEffects(): Array<{
|
|
16
|
+
id: number;
|
|
17
|
+
fn: Function;
|
|
18
|
+
stack: string;
|
|
19
|
+
}>;
|
|
20
|
+
export declare function getCurrentScopeInfo(): {
|
|
21
|
+
cleanupCount: number;
|
|
22
|
+
effectCount: number;
|
|
23
|
+
};
|