@ynor/ynor 1.0.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/README.md +16 -0
- package/bin/ynor.js +519 -0
- package/package.json +93 -0
- package/src/lib/core/compiler.d.ts +294 -0
- package/src/lib/core/compiler.js +472 -0
- package/src/lib/core/index.js +43 -0
- package/src/lib/core/plugin.js +114 -0
- package/src/lib/core/runtime.js +138 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
// src/lib/core/compiler.js
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { parse as acornParse } from 'acorn';
|
|
5
|
+
|
|
6
|
+
// ============================================
|
|
7
|
+
// VERSION
|
|
8
|
+
// ============================================
|
|
9
|
+
export const VERSION = '1.0.0';
|
|
10
|
+
|
|
11
|
+
// ============================================
|
|
12
|
+
// BASE NODE CLASS
|
|
13
|
+
// ============================================
|
|
14
|
+
class BaseNode {
|
|
15
|
+
constructor(type, start, end) {
|
|
16
|
+
this.type = type;
|
|
17
|
+
this.start = start;
|
|
18
|
+
this.end = end;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ============================================
|
|
23
|
+
// AST NAMESPACE
|
|
24
|
+
// ============================================
|
|
25
|
+
export const AST = {
|
|
26
|
+
BaseNode,
|
|
27
|
+
Root: class extends BaseNode {
|
|
28
|
+
constructor(start, end) {
|
|
29
|
+
super('Root', start, end);
|
|
30
|
+
this.options = null;
|
|
31
|
+
this.fragment = null;
|
|
32
|
+
this.css = null;
|
|
33
|
+
this.instance = null;
|
|
34
|
+
this.module = null;
|
|
35
|
+
this.comments = [];
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
Fragment: class extends BaseNode {
|
|
39
|
+
constructor(start, end) {
|
|
40
|
+
super('Fragment', start, end);
|
|
41
|
+
this.nodes = [];
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
Text: class extends BaseNode {
|
|
45
|
+
constructor(start, end, data, raw) {
|
|
46
|
+
super('Text', start, end);
|
|
47
|
+
this.data = data;
|
|
48
|
+
this.raw = raw || data;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
ExpressionTag: class extends BaseNode {
|
|
52
|
+
constructor(start, end, expression) {
|
|
53
|
+
super('ExpressionTag', start, end);
|
|
54
|
+
this.expression = expression;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// ============================================
|
|
60
|
+
// YNOR COMPILER CLASS
|
|
61
|
+
// ============================================
|
|
62
|
+
export class YnorCompiler {
|
|
63
|
+
constructor(options = {}) {
|
|
64
|
+
this.options = {
|
|
65
|
+
generate: 'dom',
|
|
66
|
+
dev: true,
|
|
67
|
+
css: 'injected',
|
|
68
|
+
...options
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
compile(content, filename) {
|
|
73
|
+
let script = '';
|
|
74
|
+
let template = '';
|
|
75
|
+
let styles = '';
|
|
76
|
+
|
|
77
|
+
console.log('📄 === COMPILING ===', filename);
|
|
78
|
+
console.log('📄 Content length:', content.length);
|
|
79
|
+
|
|
80
|
+
// Check if content is empty
|
|
81
|
+
if (!content || content.trim() === '') {
|
|
82
|
+
console.error('❌ Content is empty!');
|
|
83
|
+
template = `<div style="padding:40px;text-align:center;font-family:system-ui;color:red;">
|
|
84
|
+
<h1>Error: Empty file</h1>
|
|
85
|
+
<p>File: ${path.basename(filename)}</p>
|
|
86
|
+
</div>`;
|
|
87
|
+
return this.generateComponent(script, template, styles, filename);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Extract template using multiple methods
|
|
91
|
+
let templateStart = content.indexOf('<template>');
|
|
92
|
+
let templateEnd = content.indexOf('</template>');
|
|
93
|
+
|
|
94
|
+
if (templateStart === -1) {
|
|
95
|
+
templateStart = content.indexOf('<template ');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (templateStart !== -1 && templateEnd !== -1) {
|
|
99
|
+
const startTagEnd = content.indexOf('>', templateStart) + 1;
|
|
100
|
+
if (startTagEnd > 0 && startTagEnd < templateEnd) {
|
|
101
|
+
template = content.substring(startTagEnd, templateEnd).trim();
|
|
102
|
+
console.log('✅ Template extracted, length:', template.length);
|
|
103
|
+
} else {
|
|
104
|
+
template = content.substring(templateStart + 10, templateEnd).trim();
|
|
105
|
+
console.log('✅ Template extracted (fallback), length:', template.length);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Extract script
|
|
110
|
+
const scriptRegex = /<script\s*>([\s\S]*?)<\/script>/;
|
|
111
|
+
const scriptMatch = content.match(scriptRegex);
|
|
112
|
+
if (scriptMatch) {
|
|
113
|
+
script = scriptMatch[1].trim();
|
|
114
|
+
console.log('✅ Script extracted, length:', script.length);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Extract styles
|
|
118
|
+
const styleRegex = /<style\s*>([\s\S]*?)<\/style>/;
|
|
119
|
+
const styleMatch = content.match(styleRegex);
|
|
120
|
+
if (styleMatch) {
|
|
121
|
+
styles = styleMatch[1].trim();
|
|
122
|
+
console.log('✅ Styles extracted, length:', styles.length);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// If no template, create fallback without HTML comments
|
|
126
|
+
if (!template) {
|
|
127
|
+
console.warn('⚠️ No template found, using fallback');
|
|
128
|
+
template = `<div style="padding:40px;text-align:center;font-family:system-ui;max-width:600px;margin:20px auto;background:white;border-radius:20px;box-shadow:0 8px 30px rgba(0,0,0,0.08);">
|
|
129
|
+
<h1 style="color:#4f46e5;">⚡ .ynor</h1>
|
|
130
|
+
<p style="color:#1e293b;font-size:1.2rem;">Component: ${path.basename(filename)}</p>
|
|
131
|
+
<div style="background:#fef2f2;padding:20px;border-radius:12px;margin:20px;border:2px solid #fecaca;">
|
|
132
|
+
<p style="color:#dc2626;font-weight:600;">No template found</p>
|
|
133
|
+
<p style="color:#64748b;font-size:0.9rem;">Expected to find template section</p>
|
|
134
|
+
<p style="color:#64748b;font-size:0.8rem;">File: ${path.basename(filename)}</p>
|
|
135
|
+
<p style="color:#64748b;font-size:0.8rem;">Content length: ${content.length} chars</p>
|
|
136
|
+
</div>
|
|
137
|
+
</div>`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return this.generateComponent(script, template, styles, filename);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/lib/core/compiler.js - Update the generateComponent method
|
|
144
|
+
|
|
145
|
+
generateComponent(script, template, styles, filename) {
|
|
146
|
+
const componentName = path.basename(filename, '.ynor');
|
|
147
|
+
const className = `Ynor${componentName}`;
|
|
148
|
+
const processedTemplate = this.processTemplate(template);
|
|
149
|
+
const runtimePath = this.getRuntimePath(filename);
|
|
150
|
+
|
|
151
|
+
console.log(`🔧 Generating component: ${componentName}`);
|
|
152
|
+
|
|
153
|
+
return `
|
|
154
|
+
// Generated from ${filename}
|
|
155
|
+
import { YnorComponent, runtime } from '${runtimePath}';
|
|
156
|
+
|
|
157
|
+
export default class ${className} extends YnorComponent {
|
|
158
|
+
constructor(options) {
|
|
159
|
+
super(options);
|
|
160
|
+
this.name = '${componentName}';
|
|
161
|
+
this._setup();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_setup() {
|
|
165
|
+
// Execute script in the context of 'this'
|
|
166
|
+
// This ensures all variables and functions are attached to the component instance
|
|
167
|
+
(function() {
|
|
168
|
+
// Define variables on 'this'
|
|
169
|
+
${script}
|
|
170
|
+
}).call(this);
|
|
171
|
+
|
|
172
|
+
// Log for debugging
|
|
173
|
+
console.log('🔧 Component setup complete:', this);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
render() {
|
|
177
|
+
const template = \`${processedTemplate}\`;
|
|
178
|
+
const container = document.createElement('div');
|
|
179
|
+
container.className = 'ynor-component ynor-${componentName}';
|
|
180
|
+
container.innerHTML = template;
|
|
181
|
+
|
|
182
|
+
this._bindEvents(container);
|
|
183
|
+
this._bindInputs(container);
|
|
184
|
+
|
|
185
|
+
this._element = container;
|
|
186
|
+
return this._element;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
_renderComponent(componentName) {
|
|
190
|
+
const Component = runtime.components.get(componentName);
|
|
191
|
+
if (!Component) {
|
|
192
|
+
console.warn(\`Component "\${componentName}" not found\`);
|
|
193
|
+
return '';
|
|
194
|
+
}
|
|
195
|
+
const tempDiv = document.createElement('div');
|
|
196
|
+
const instance = new Component({ target: tempDiv });
|
|
197
|
+
instance.$mount();
|
|
198
|
+
return tempDiv.innerHTML;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
_bindEvents(container) {
|
|
202
|
+
const clickElements = container.querySelectorAll('[data-on-click]');
|
|
203
|
+
clickElements.forEach(el => {
|
|
204
|
+
const handlerName = el.getAttribute('data-on-click');
|
|
205
|
+
if (this[handlerName]) {
|
|
206
|
+
el.addEventListener('click', this[handlerName].bind(this));
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const changeElements = container.querySelectorAll('[data-on-change]');
|
|
211
|
+
changeElements.forEach(el => {
|
|
212
|
+
const handlerName = el.getAttribute('data-on-change');
|
|
213
|
+
if (this[handlerName]) {
|
|
214
|
+
el.addEventListener('change', this[handlerName].bind(this));
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const keydownElements = container.querySelectorAll('[data-on-keydown]');
|
|
219
|
+
keydownElements.forEach(el => {
|
|
220
|
+
const handlerName = el.getAttribute('data-on-keydown');
|
|
221
|
+
if (this[handlerName]) {
|
|
222
|
+
el.addEventListener('keydown', this[handlerName].bind(this));
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
_bindInputs(container) {
|
|
228
|
+
const inputs = container.querySelectorAll('[data-bind]');
|
|
229
|
+
inputs.forEach(el => {
|
|
230
|
+
const variable = el.getAttribute('data-bind');
|
|
231
|
+
if (this[variable] !== undefined) {
|
|
232
|
+
el.value = this[variable];
|
|
233
|
+
el.addEventListener('input', (e) => {
|
|
234
|
+
this[variable] = e.target.value;
|
|
235
|
+
this._update();
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
_update() {
|
|
242
|
+
if (this._destroyed) return;
|
|
243
|
+
this.render();
|
|
244
|
+
if (this._element && this.target) {
|
|
245
|
+
this.target.innerHTML = '';
|
|
246
|
+
this.target.appendChild(this._element);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
get styles() {
|
|
251
|
+
return \`${styles}\`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
$mount(target) {
|
|
255
|
+
super.$mount(target);
|
|
256
|
+
this._injectStyles();
|
|
257
|
+
return this;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
_injectStyles() {
|
|
261
|
+
if (this.styles && !document.getElementById('ynor-${componentName}-styles')) {
|
|
262
|
+
const styleEl = document.createElement('style');
|
|
263
|
+
styleEl.id = 'ynor-${componentName}-styles';
|
|
264
|
+
styleEl.textContent = this.styles;
|
|
265
|
+
document.head.appendChild(styleEl);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
runtime.registerComponent('${componentName}', ${className});
|
|
271
|
+
`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
transformScript(script) {
|
|
276
|
+
if (!script) return '';
|
|
277
|
+
|
|
278
|
+
// Simple approach: wrap the script in a function that binds to this
|
|
279
|
+
// This is safer than trying to parse and transform each line
|
|
280
|
+
return `
|
|
281
|
+
// Auto-bind variables and functions to this
|
|
282
|
+
const self = this;
|
|
283
|
+
|
|
284
|
+
// Simple variables that will be attached to this
|
|
285
|
+
${script}
|
|
286
|
+
|
|
287
|
+
// After script execution, attach all local variables to this
|
|
288
|
+
// This captures any let/const/var declarations
|
|
289
|
+
const localVars = Object.keys(this).filter(key => key !== 'self');
|
|
290
|
+
for (const key of localVars) {
|
|
291
|
+
if (this[key] !== undefined && typeof this[key] !== 'function') {
|
|
292
|
+
// Already attached
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
getRuntimePath(filename) {
|
|
299
|
+
const normalizedPath = filename.replace(/\\/g, '/');
|
|
300
|
+
const fileDir = path.dirname(normalizedPath);
|
|
301
|
+
const coreDir = path.join(process.cwd(), 'src', 'lib', 'core');
|
|
302
|
+
|
|
303
|
+
let relativePath = path.relative(fileDir, coreDir);
|
|
304
|
+
relativePath = relativePath.replace(/\\/g, '/');
|
|
305
|
+
|
|
306
|
+
if (!relativePath.startsWith('.')) {
|
|
307
|
+
relativePath = './' + relativePath;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (relativePath === './') {
|
|
311
|
+
relativePath = '.';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return relativePath + '/runtime.js';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/lib/core/compiler.js - Fix processTemplate
|
|
318
|
+
|
|
319
|
+
processTemplate(template) {
|
|
320
|
+
if (!template) {
|
|
321
|
+
return '';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
let processed = template;
|
|
325
|
+
|
|
326
|
+
// Handle component tags
|
|
327
|
+
processed = processed.replace(/<([A-Z][a-zA-Z0-9]*)\s*\/>/g, (match, componentName) => {
|
|
328
|
+
return '${this._renderComponent("' + componentName + '")}';
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Handle on:click={handler} - FIX THIS
|
|
332
|
+
processed = processed.replace(/on:([a-zA-Z]+)=\{([^}]+)\}/g, (match, event, handler) => {
|
|
333
|
+
// Store the handler name as a data attribute
|
|
334
|
+
return `data-on-${event}="${handler.trim()}"`;
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Handle expressions {variable}
|
|
338
|
+
processed = processed.replace(/\{([^}]+)\}/g, (match, expr) => {
|
|
339
|
+
const trimmed = expr.trim();
|
|
340
|
+
// For simple variables, access from this
|
|
341
|
+
return '${this.' + trimmed + ' !== undefined ? this.' + trimmed + ' : 0}';
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Handle class:active={condition}
|
|
345
|
+
processed = processed.replace(/class:([a-zA-Z-]+)=\{([^}]+)\}/g, (match, className, condition) => {
|
|
346
|
+
return '${' + condition.trim() + ' ? "' + className + '" : ""}';
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Handle bind:value={variable}
|
|
350
|
+
processed = processed.replace(/bind:value=\{([^}]+)\}/g, (match, variable) => {
|
|
351
|
+
return `data-bind="${variable.trim()}" value="\${this.${variable.trim()}}"`;
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Handle #each
|
|
355
|
+
processed = processed.replace(/{#each\s+([^}]+)\s+as\s+([^}]+)}/g, (match, iterable, item) => {
|
|
356
|
+
return '${' + iterable.trim() + '.map(' + item.trim() + ' => `';
|
|
357
|
+
});
|
|
358
|
+
processed = processed.replace(/{\/each}/g, '`).join("")}');
|
|
359
|
+
|
|
360
|
+
// Handle #if with :else if and :else
|
|
361
|
+
processed = processed.replace(/{:else if\s+([^}]+)}/g, (match, condition) => {
|
|
362
|
+
return '` : (' + condition.trim() + ' ? `';
|
|
363
|
+
});
|
|
364
|
+
processed = processed.replace(/{:else}/g, '` : `');
|
|
365
|
+
processed = processed.replace(/{#if\s+([^}]+)}/g, (match, condition) => {
|
|
366
|
+
return '${' + condition.trim() + ' ? `';
|
|
367
|
+
});
|
|
368
|
+
processed = processed.replace(/{\/if}/g, '` : ""}');
|
|
369
|
+
|
|
370
|
+
console.log('📝 Processed template:', processed);
|
|
371
|
+
|
|
372
|
+
return processed;
|
|
373
|
+
}}
|
|
374
|
+
|
|
375
|
+
// ============================================
|
|
376
|
+
// PARSER
|
|
377
|
+
// ============================================
|
|
378
|
+
export function parse(source, options = {}) {
|
|
379
|
+
const filename = options.filename || 'unknown.ynor';
|
|
380
|
+
const root = new AST.Root(0, source.length);
|
|
381
|
+
|
|
382
|
+
const scriptMatch = source.match(/<script\s*>([\s\S]*?)<\/script>/);
|
|
383
|
+
if (scriptMatch) {
|
|
384
|
+
try {
|
|
385
|
+
const program = acornParse(scriptMatch[1], {
|
|
386
|
+
ecmaVersion: 2022,
|
|
387
|
+
sourceType: 'module',
|
|
388
|
+
locations: true,
|
|
389
|
+
sourceFilename: filename
|
|
390
|
+
});
|
|
391
|
+
root.instance = { content: program, context: 'default', attributes: [] };
|
|
392
|
+
} catch (e) {
|
|
393
|
+
console.warn('Failed to parse script:', e.message);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return options.modern ? root : root;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ============================================
|
|
401
|
+
// COMPILE
|
|
402
|
+
// ============================================
|
|
403
|
+
export function compile(source, options = {}) {
|
|
404
|
+
const filename = options.filename || 'component.ynor';
|
|
405
|
+
const compiler = new YnorCompiler(options);
|
|
406
|
+
const compiled = compiler.compile(source, filename);
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
js: { code: compiled, map: null },
|
|
410
|
+
css: { code: '', map: null },
|
|
411
|
+
ast: parse(source, { ...options, modern: true }),
|
|
412
|
+
warnings: [],
|
|
413
|
+
vars: []
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ============================================
|
|
418
|
+
// OTHER EXPORTS
|
|
419
|
+
// ============================================
|
|
420
|
+
export function compileModule(source, options = {}) {
|
|
421
|
+
try {
|
|
422
|
+
const ast = acornParse(source, {
|
|
423
|
+
ecmaVersion: 2022,
|
|
424
|
+
sourceType: 'module',
|
|
425
|
+
locations: true
|
|
426
|
+
});
|
|
427
|
+
return { js: { code: source, map: null }, ast, warnings: [], vars: [] };
|
|
428
|
+
} catch (error) {
|
|
429
|
+
throw new Error(`Failed to compile module: ${error.message}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export function migrate(source) {
|
|
434
|
+
let code = source;
|
|
435
|
+
code = code.replace(/{#each\s+([^}]+)\s+as\s+([^}]+)}/g, (match, iterable, item) => {
|
|
436
|
+
return `{#each ${iterable} as ${item}}`;
|
|
437
|
+
});
|
|
438
|
+
code = code.replace(/{#if\s+([^}]+)}/g, (match, condition) => {
|
|
439
|
+
return `{#if ${condition}}`;
|
|
440
|
+
});
|
|
441
|
+
return { code };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function preprocess(source, preprocessor) {
|
|
445
|
+
return Promise.resolve({ code: source, toString: () => source });
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export function print(ast) {
|
|
449
|
+
return { code: '', map: null };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export function parseCss(source) {
|
|
453
|
+
return { type: 'StyleSheet', nodes: [] };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export function walk(ast, options) {
|
|
457
|
+
throw new Error('Please import { walk } from "estree-walker" instead');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export default {
|
|
461
|
+
VERSION,
|
|
462
|
+
compile,
|
|
463
|
+
compileModule,
|
|
464
|
+
migrate,
|
|
465
|
+
parse,
|
|
466
|
+
parseCss,
|
|
467
|
+
preprocess,
|
|
468
|
+
print,
|
|
469
|
+
walk,
|
|
470
|
+
AST,
|
|
471
|
+
YnorCompiler
|
|
472
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/lib/core/index.js
|
|
2
|
+
export {
|
|
3
|
+
VERSION,
|
|
4
|
+
compile,
|
|
5
|
+
compileModule,
|
|
6
|
+
migrate,
|
|
7
|
+
parse,
|
|
8
|
+
parseCss,
|
|
9
|
+
preprocess,
|
|
10
|
+
print,
|
|
11
|
+
walk,
|
|
12
|
+
AST,
|
|
13
|
+
YnorCompiler
|
|
14
|
+
} from './compiler.js';
|
|
15
|
+
|
|
16
|
+
export { ynorPlugin } from './plugin.js';
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
YnorRuntime,
|
|
20
|
+
YnorComponent,
|
|
21
|
+
runtime,
|
|
22
|
+
store,
|
|
23
|
+
mount
|
|
24
|
+
} from './runtime.js';
|
|
25
|
+
|
|
26
|
+
// Default export
|
|
27
|
+
export default {
|
|
28
|
+
VERSION,
|
|
29
|
+
compile,
|
|
30
|
+
compileModule,
|
|
31
|
+
migrate,
|
|
32
|
+
parse,
|
|
33
|
+
parseCss,
|
|
34
|
+
preprocess,
|
|
35
|
+
print,
|
|
36
|
+
walk,
|
|
37
|
+
AST,
|
|
38
|
+
YnorCompiler,
|
|
39
|
+
ynorPlugin,
|
|
40
|
+
runtime,
|
|
41
|
+
store,
|
|
42
|
+
mount
|
|
43
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// src/lib/core/plugin.js
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { YnorCompiler } from './compiler.js';
|
|
5
|
+
|
|
6
|
+
export function ynorPlugin(options = {}) {
|
|
7
|
+
const compiler = new YnorCompiler(options);
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
name: 'vite-plugin-ynor',
|
|
11
|
+
enforce: 'pre',
|
|
12
|
+
|
|
13
|
+
resolveId(id) {
|
|
14
|
+
if (id.endsWith('.ynor')) {
|
|
15
|
+
const root = process.cwd();
|
|
16
|
+
const possiblePaths = [
|
|
17
|
+
path.resolve(root, 'src', id),
|
|
18
|
+
path.resolve(root, 'src', 'lib', 'components', id),
|
|
19
|
+
path.resolve(root, id),
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
if (id.includes('/') || id.includes('\\')) {
|
|
23
|
+
possiblePaths.unshift(path.resolve(root, id));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const p of possiblePaths) {
|
|
27
|
+
try {
|
|
28
|
+
if (fs.existsSync(p)) {
|
|
29
|
+
return p;
|
|
30
|
+
}
|
|
31
|
+
} catch (e) {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.warn(`⚠️ .ynor file not found: ${id}`);
|
|
35
|
+
return id;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
load(id) {
|
|
41
|
+
if (id.endsWith('.ynor')) {
|
|
42
|
+
try {
|
|
43
|
+
let filePath = id;
|
|
44
|
+
if (id.startsWith('\0')) {
|
|
45
|
+
filePath = id.slice(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (filePath.startsWith('virtual:')) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!fs.existsSync(filePath)) {
|
|
53
|
+
console.warn(`⚠️ .ynor file not found: ${filePath}`);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
58
|
+
const compiled = compiler.compile(content, filePath);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
code: compiled,
|
|
62
|
+
map: null
|
|
63
|
+
};
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(`❌ Error compiling .ynor file: ${id}`, error.message);
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
transform(code, id) {
|
|
73
|
+
if (id.endsWith('.ynor')) {
|
|
74
|
+
try {
|
|
75
|
+
let filePath = id;
|
|
76
|
+
if (id.startsWith('\0')) {
|
|
77
|
+
filePath = id.slice(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (filePath.startsWith('virtual:')) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Always read fresh from disk
|
|
85
|
+
if (fs.existsSync(filePath)) {
|
|
86
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
87
|
+
const compiled = compiler.compile(content, filePath);
|
|
88
|
+
return {
|
|
89
|
+
code: compiled,
|
|
90
|
+
map: null
|
|
91
|
+
};
|
|
92
|
+
} else {
|
|
93
|
+
console.warn(`⚠️ .ynor file not found for transform: ${filePath}`);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(`❌ Error transforming .ynor file: ${id}`, error.message);
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
handleHotUpdate({ file, server }) {
|
|
105
|
+
if (file.endsWith('.ynor')) {
|
|
106
|
+
console.log(`🔄 Hot reload .ynor file: ${file}`);
|
|
107
|
+
server.ws.send({
|
|
108
|
+
type: 'full-reload',
|
|
109
|
+
path: file
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|