@fluffjs/cli 0.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.
@@ -0,0 +1,411 @@
1
+ import { types as t } from '@babel/core';
2
+ export const reactivePropertiesMap = new Map();
3
+ export default function reactivePlugin() {
4
+ return {
5
+ name: 'babel-plugin-reactive', visitor: {
6
+ Program: {
7
+ enter(path, state) {
8
+ state.needsPropertyImport = false;
9
+ state.reactiveProperties = new Set();
10
+ state.watchMethods = [];
11
+ }, exit(path, state) {
12
+ const filename = state.filename ?? 'unknown';
13
+ if (state.reactiveProperties && state.reactiveProperties.size > 0) {
14
+ reactivePropertiesMap.set(filename, state.reactiveProperties);
15
+ }
16
+ if (state.needsPropertyImport) {
17
+ const hasPropertyImport = path.node.body.some(node => {
18
+ if (t.isImportDeclaration(node)) {
19
+ return node.specifiers.some(spec => t.isImportSpecifier(spec) && t.isIdentifier(spec.imported) && spec.imported.name === 'Property');
20
+ }
21
+ return false;
22
+ });
23
+ if (!hasPropertyImport) {
24
+ const importDecl = t.importDeclaration([t.importSpecifier(t.identifier('Property'), t.identifier('Property'))], t.stringLiteral('@fluffjs/fluff'));
25
+ path.node.body.unshift(importDecl);
26
+ }
27
+ }
28
+ }
29
+ },
30
+ ClassBody(path, state) {
31
+ const watchCalls = [];
32
+ const watchCallProps = [];
33
+ for (const memberPath of path.get('body')) {
34
+ if (!memberPath.isClassProperty())
35
+ continue;
36
+ const init = memberPath.node.value;
37
+ if (!init || !t.isCallExpression(init))
38
+ continue;
39
+ const { callee } = init;
40
+ if (!t.isMemberExpression(callee))
41
+ continue;
42
+ if (!t.isThisExpression(callee.object))
43
+ continue;
44
+ if (!t.isIdentifier(callee.property) || callee.property.name !== '$watch')
45
+ continue;
46
+ const propName = t.isIdentifier(memberPath.node.key) ? memberPath.node.key.name : null;
47
+ if (!propName)
48
+ continue;
49
+ const args = init.arguments;
50
+ if (args.length < 2)
51
+ continue;
52
+ const [propsArg, callbackArg] = args;
53
+ if (!t.isArrayExpression(propsArg))
54
+ continue;
55
+ const watchedProps = propsArg.elements
56
+ .filter((el) => t.isStringLiteral(el))
57
+ .map(el => el.value);
58
+ watchCalls.push({ propName, watchedProps, callbackArg });
59
+ watchCallProps.push(memberPath);
60
+ }
61
+ for (const p of watchCallProps) {
62
+ p.remove();
63
+ }
64
+ const newMembers = [];
65
+ const propsToRemove = [];
66
+ const watchMethods = [];
67
+ const privateFields = [];
68
+ const getterHostBindingUpdates = [];
69
+ const propertyHostBindingInits = [];
70
+ const pipeMethods = [];
71
+ const hostListeners = [];
72
+ for (const memberPath of path.get('body')) {
73
+ if (memberPath.isClassMethod()) {
74
+ const methodNode = memberPath.node;
75
+ const decorators = methodNode.decorators ?? [];
76
+ const hostListenerIndex = decorators.findIndex(dec => {
77
+ if (t.isCallExpression(dec.expression) && t.isIdentifier(dec.expression.callee)) {
78
+ return dec.expression.callee.name === 'HostListener';
79
+ }
80
+ return false;
81
+ });
82
+ if (hostListenerIndex >= 0) {
83
+ const hostListenerDecorator = decorators[hostListenerIndex];
84
+ if (t.isCallExpression(hostListenerDecorator.expression)) {
85
+ const args = hostListenerDecorator.expression.arguments;
86
+ if (args.length > 0 && t.isStringLiteral(args[0])) {
87
+ const eventName = args[0].value;
88
+ if (t.isIdentifier(methodNode.key)) {
89
+ hostListeners.push({
90
+ methodName: methodNode.key.name, eventName
91
+ });
92
+ }
93
+ }
94
+ }
95
+ decorators.splice(hostListenerIndex, 1);
96
+ }
97
+ const pipeDecoratorIndex = decorators.findIndex(dec => {
98
+ if (t.isCallExpression(dec.expression) && t.isIdentifier(dec.expression.callee)) {
99
+ return dec.expression.callee.name === 'Pipe';
100
+ }
101
+ return false;
102
+ });
103
+ if (pipeDecoratorIndex >= 0) {
104
+ const pipeDecorator = decorators[pipeDecoratorIndex];
105
+ if (t.isCallExpression(pipeDecorator.expression)) {
106
+ const args = pipeDecorator.expression.arguments;
107
+ if (args.length > 0 && t.isStringLiteral(args[0])) {
108
+ const pipeName = args[0].value;
109
+ if (t.isIdentifier(methodNode.key)) {
110
+ pipeMethods.push({
111
+ pipeName, methodName: methodNode.key.name
112
+ });
113
+ }
114
+ }
115
+ }
116
+ decorators.splice(pipeDecoratorIndex, 1);
117
+ }
118
+ const watchDecoratorIndex = decorators.findIndex(dec => {
119
+ if (t.isCallExpression(dec.expression) && t.isIdentifier(dec.expression.callee)) {
120
+ return dec.expression.callee.name === 'Watch';
121
+ }
122
+ return false;
123
+ });
124
+ if (watchDecoratorIndex >= 0) {
125
+ const watchDecorator = decorators[watchDecoratorIndex];
126
+ if (t.isCallExpression(watchDecorator.expression)) {
127
+ const args = watchDecorator.expression.arguments;
128
+ const watchedProps = args
129
+ .filter((arg) => t.isStringLiteral(arg))
130
+ .map(arg => arg.value);
131
+ if (t.isIdentifier(methodNode.key) && watchedProps.length > 0) {
132
+ watchMethods.push({
133
+ methodName: methodNode.key.name, watchedProps
134
+ });
135
+ }
136
+ }
137
+ decorators.splice(watchDecoratorIndex, 1);
138
+ }
139
+ if (methodNode.kind === 'get') {
140
+ const hostBindingIndex = decorators.findIndex(dec => {
141
+ if (t.isCallExpression(dec.expression) && t.isIdentifier(dec.expression.callee)) {
142
+ return dec.expression.callee.name === 'HostBinding';
143
+ }
144
+ return false;
145
+ });
146
+ if (hostBindingIndex >= 0) {
147
+ const hostBindingDecorator = decorators[hostBindingIndex];
148
+ if (t.isCallExpression(hostBindingDecorator.expression)) {
149
+ const args = hostBindingDecorator.expression.arguments;
150
+ if (args.length > 0 && t.isStringLiteral(args[0]) && t.isIdentifier(methodNode.key)) {
151
+ const hostProperty = args[0].value;
152
+ const getterName = methodNode.key.name;
153
+ let updateStatement = t.emptyStatement();
154
+ if (hostProperty.startsWith('class.')) {
155
+ const className = hostProperty.slice(6);
156
+ updateStatement = t.ifStatement(t.identifier('__v'), t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier('classList')), t.identifier('add')), [t.stringLiteral(className)])), t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier('classList')), t.identifier('remove')), [t.stringLiteral(className)])));
157
+ }
158
+ else if (hostProperty.startsWith('attr.')) {
159
+ const attrName = hostProperty.slice(5);
160
+ updateStatement = t.ifStatement(t.binaryExpression('!=', t.identifier('__v'), t.nullLiteral()), t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('setAttribute')), [
161
+ t.stringLiteral(attrName),
162
+ t.callExpression(t.identifier('String'), [t.identifier('__v')])
163
+ ])), t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('removeAttribute')), [t.stringLiteral(attrName)])));
164
+ }
165
+ else if (hostProperty.startsWith('style.')) {
166
+ const styleProp = hostProperty.slice(6);
167
+ updateStatement = t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier('style')), t.identifier(styleProp)), t.logicalExpression('||', t.identifier('__v'), t.stringLiteral(''))));
168
+ }
169
+ else {
170
+ updateStatement = t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.thisExpression(), t.identifier(hostProperty)), t.identifier('__v')));
171
+ }
172
+ const updateMethod = t.classMethod('method', t.identifier(`__updateHostBinding_${getterName}`), [], t.blockStatement([
173
+ t.variableDeclaration('const', [t.variableDeclarator(t.identifier('__v'), t.memberExpression(t.thisExpression(), t.identifier(getterName)))]),
174
+ updateStatement
175
+ ]));
176
+ newMembers.push(updateMethod);
177
+ getterHostBindingUpdates.push(`__updateHostBinding_${getterName}`);
178
+ }
179
+ }
180
+ decorators.splice(hostBindingIndex, 1);
181
+ }
182
+ }
183
+ continue;
184
+ }
185
+ if (!memberPath.isClassProperty())
186
+ continue;
187
+ const propNode = memberPath.node;
188
+ const decorators = propNode.decorators ?? [];
189
+ const hostBindingDecoratorIndex = decorators.findIndex(dec => {
190
+ if (t.isCallExpression(dec.expression) && t.isIdentifier(dec.expression.callee)) {
191
+ return dec.expression.callee.name === 'HostBinding';
192
+ }
193
+ return false;
194
+ });
195
+ if (hostBindingDecoratorIndex >= 0) {
196
+ const hostBindingDecorator = decorators[hostBindingDecoratorIndex];
197
+ if (t.isCallExpression(hostBindingDecorator.expression)) {
198
+ const args = hostBindingDecorator.expression.arguments;
199
+ if (args.length > 0 && t.isStringLiteral(args[0]) && t.isIdentifier(propNode.key)) {
200
+ const hostProperty = args[0].value;
201
+ const propName = propNode.key.name;
202
+ const privateName = `__hostBinding_${propName}`;
203
+ const initialValue = propNode.value ?? t.identifier('undefined');
204
+ const privateField = t.classProperty(t.identifier(privateName), initialValue);
205
+ const getter = t.classMethod('get', t.identifier(propName), [], t.blockStatement([
206
+ t.returnStatement(t.memberExpression(t.thisExpression(), t.identifier(privateName)))
207
+ ]));
208
+ let updateStatement = t.emptyStatement();
209
+ if (hostProperty.startsWith('class.')) {
210
+ const className = hostProperty.slice(6);
211
+ updateStatement = t.ifStatement(t.identifier('__v'), t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier('classList')), t.identifier('add')), [t.stringLiteral(className)])), t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier('classList')), t.identifier('remove')), [t.stringLiteral(className)])));
212
+ }
213
+ else if (hostProperty.startsWith('attr.')) {
214
+ const attrName = hostProperty.slice(5);
215
+ updateStatement = t.ifStatement(t.binaryExpression('!=', t.identifier('__v'), t.nullLiteral()), t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('setAttribute')), [
216
+ t.stringLiteral(attrName),
217
+ t.callExpression(t.identifier('String'), [t.identifier('__v')])
218
+ ])), t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('removeAttribute')), [t.stringLiteral(attrName)])));
219
+ }
220
+ else if (hostProperty.startsWith('style.')) {
221
+ const styleProp = hostProperty.slice(6);
222
+ updateStatement = t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier('style')), t.identifier(styleProp)), t.logicalExpression('||', t.identifier('__v'), t.stringLiteral(''))));
223
+ }
224
+ else {
225
+ updateStatement = t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.thisExpression(), t.identifier(hostProperty)), t.identifier('__v')));
226
+ }
227
+ const setter = t.classMethod('set', t.identifier(propName), [t.identifier('__v')], t.blockStatement([
228
+ t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.thisExpression(), t.identifier(privateName)), t.identifier('__v'))),
229
+ updateStatement
230
+ ]));
231
+ newMembers.push(getter, setter);
232
+ propsToRemove.push(memberPath);
233
+ propertyHostBindingInits.push({ propName, privateName });
234
+ path.unshiftContainer('body', privateField);
235
+ }
236
+ }
237
+ continue;
238
+ }
239
+ const viewChildDecoratorIndex = decorators.findIndex(dec => {
240
+ if (t.isCallExpression(dec.expression) && t.isIdentifier(dec.expression.callee)) {
241
+ return dec.expression.callee.name === 'ViewChild';
242
+ }
243
+ return false;
244
+ });
245
+ if (viewChildDecoratorIndex >= 0) {
246
+ const viewChildDecorator = decorators[viewChildDecoratorIndex];
247
+ if (t.isCallExpression(viewChildDecorator.expression)) {
248
+ const args = viewChildDecorator.expression.arguments;
249
+ if (args.length > 0 && t.isStringLiteral(args[0]) && t.isIdentifier(propNode.key)) {
250
+ const selector = args[0].value;
251
+ const propName = propNode.key.name;
252
+ const getter = t.classMethod('get', t.identifier(propName), [], t.blockStatement([
253
+ t.returnStatement(t.logicalExpression('||', t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__getShadowRoot')), []), t.identifier('querySelector')), [t.stringLiteral(`[data-ref="${selector}"]`)]), t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__getShadowRoot')), []), t.identifier('querySelector')), [t.stringLiteral(selector)])))
254
+ ]));
255
+ newMembers.push(getter);
256
+ propsToRemove.push(memberPath);
257
+ }
258
+ }
259
+ continue;
260
+ }
261
+ const reactiveDecoratorIndices = [];
262
+ for (const [idx, dec] of decorators.entries()) {
263
+ const name = t.isCallExpression(dec.expression) && t.isIdentifier(dec.expression.callee) ? dec.expression.callee.name : t.isIdentifier(dec.expression) ? dec.expression.name : null;
264
+ if (name === 'Reactive' || name === 'Input') {
265
+ reactiveDecoratorIndices.push(idx);
266
+ }
267
+ }
268
+ if (reactiveDecoratorIndices.length === 0)
269
+ continue;
270
+ for (let i = reactiveDecoratorIndices.length - 1; i >= 0; i--) {
271
+ decorators.splice(reactiveDecoratorIndices[i], 1);
272
+ }
273
+ if (!t.isIdentifier(propNode.key))
274
+ continue;
275
+ const propName = propNode.key.name;
276
+ const privateName = `__${propName}`;
277
+ const initialValue = propNode.value ?? t.identifier('undefined');
278
+ state.needsPropertyImport = true;
279
+ state.reactiveProperties?.add(propName);
280
+ const privateField = t.classProperty(t.identifier(privateName), t.newExpression(t.identifier('Property'), [initialValue]));
281
+ const getter = t.classMethod('get', t.identifier(propName), [], t.blockStatement([
282
+ t.returnStatement(t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(privateName)), t.identifier('getValue')), []))
283
+ ]));
284
+ const setter = t.classMethod('set', t.identifier(propName), [t.identifier('__v')], t.blockStatement([
285
+ t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(privateName)), t.identifier('setValue')), [t.identifier('__v')]))
286
+ ]));
287
+ propsToRemove.push(memberPath);
288
+ newMembers.push(getter, setter);
289
+ privateFields.push(privateField);
290
+ }
291
+ for (const p of propsToRemove) {
292
+ p.remove();
293
+ }
294
+ for (const field of privateFields.reverse()) {
295
+ path.unshiftContainer('body', field);
296
+ }
297
+ for (const member of newMembers.reverse()) {
298
+ path.unshiftContainer('body', member);
299
+ }
300
+ if (pipeMethods.length > 0) {
301
+ const pipeProperties = pipeMethods.map(({ pipeName, methodName }) => t.objectProperty(t.identifier(pipeName), t.arrowFunctionExpression([t.restElement(t.identifier('args'))], t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(methodName)), t.identifier('apply')), [
302
+ t.thisExpression(),
303
+ t.identifier('args')
304
+ ]))));
305
+ const pipesProperty = t.classProperty(t.identifier('__pipes'), t.objectExpression(pipeProperties));
306
+ path.unshiftContainer('body', pipesProperty);
307
+ }
308
+ const classDecl = path.parentPath;
309
+ const classNode = classDecl?.node;
310
+ const decorators = (classNode && 'decorators' in classNode ? classNode.decorators : null) ?? [];
311
+ const componentDecorator = decorators.find((dec) => t.isCallExpression(dec.expression) && t.isIdentifier(dec.expression.callee) && dec.expression.callee.name === 'Component');
312
+ if (componentDecorator && t.isCallExpression(componentDecorator.expression)) {
313
+ const args = componentDecorator.expression.arguments;
314
+ if (args.length > 0 && t.isObjectExpression(args[0])) {
315
+ const [configObj] = args;
316
+ const pipesProp = configObj.properties.find(p => t.isObjectProperty(p) && t.isIdentifier(p.key) && p.key.name === 'pipes');
317
+ if (pipesProp && t.isObjectProperty(pipesProp) && t.isArrayExpression(pipesProp.value)) {
318
+ const pipeClasses = pipesProp.value.elements.filter((el) => t.isIdentifier(el));
319
+ if (pipeClasses.length > 0) {
320
+ const simplePipeProperties = pipeClasses.map(pipeClass => t.objectProperty(t.memberExpression(pipeClass, t.identifier('__pipeName')), t.arrowFunctionExpression([
321
+ t.identifier('v'),
322
+ t.restElement(t.identifier('a'))
323
+ ], t.callExpression(t.memberExpression(t.newExpression(pipeClass, []), t.identifier('transform')), [
324
+ t.identifier('v'),
325
+ t.spreadElement(t.identifier('a'))
326
+ ])), true));
327
+ const pipesFromClasses = t.classProperty(t.identifier('__pipes'), t.objectExpression(simplePipeProperties));
328
+ path.unshiftContainer('body', pipesFromClasses);
329
+ }
330
+ }
331
+ }
332
+ }
333
+ const reactiveProps = state.reactiveProperties ?? new Set();
334
+ const constructorStatements = [];
335
+ for (const updateMethodName of getterHostBindingUpdates) {
336
+ constructorStatements.push(t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier(updateMethodName)), [])));
337
+ }
338
+ for (const { propName, privateName } of propertyHostBindingInits) {
339
+ constructorStatements.push(t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.thisExpression(), t.identifier(propName)), t.memberExpression(t.thisExpression(), t.identifier(privateName)))));
340
+ }
341
+ for (const { methodName, watchedProps: mProps } of watchMethods) {
342
+ constructorStatements.push(t.expressionStatement(t.unaryExpression('void', t.memberExpression(t.thisExpression(), t.identifier(methodName)))));
343
+ for (const prop of mProps) {
344
+ if (reactiveProps.has(prop)) {
345
+ constructorStatements.push(t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(`__${prop}`)), t.identifier('onChange')), t.identifier('subscribe')), [t.arrowFunctionExpression([], t.callExpression(t.memberExpression(t.thisExpression(), t.identifier(methodName)), []))])));
346
+ }
347
+ }
348
+ }
349
+ for (const { propName, watchedProps: wProps, callbackArg } of watchCalls) {
350
+ const iife = [
351
+ t.variableDeclaration('const', [t.variableDeclarator(t.identifier('__cb'), t.isExpression(callbackArg) ? callbackArg : t.identifier('undefined'))]),
352
+ t.variableDeclaration('const', [t.variableDeclarator(t.identifier('__subs'), t.arrayExpression([]))])
353
+ ];
354
+ for (const prop of wProps) {
355
+ if (reactiveProps.has(prop)) {
356
+ iife.push(t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('__subs'), t.identifier('push')), [
357
+ t.callExpression(t.memberExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(`__${prop}`)), t.identifier('onChange')), t.identifier('subscribe')), [t.identifier('__cb')])
358
+ ])));
359
+ }
360
+ }
361
+ iife.push(t.expressionStatement(t.callExpression(t.identifier('__cb'), [])));
362
+ iife.push(t.returnStatement(t.objectExpression([
363
+ t.objectMethod('method', t.identifier('unsubscribe'), [], t.blockStatement([
364
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('__subs'), t.identifier('forEach')), [
365
+ t.arrowFunctionExpression([t.identifier('s')], t.callExpression(t.memberExpression(t.identifier('s'), t.identifier('unsubscribe')), []))
366
+ ]))
367
+ ]))
368
+ ])));
369
+ const watchClassProp = t.classProperty(t.identifier(propName), t.callExpression(t.arrowFunctionExpression([], t.blockStatement(iife)), []));
370
+ path.pushContainer('body', watchClassProp);
371
+ }
372
+ for (const { methodName, eventName } of hostListeners) {
373
+ const [baseEvent, ...modifiers] = eventName.split('.');
374
+ let handlerBody = t.emptyStatement();
375
+ if (modifiers.length > 0) {
376
+ const conditions = modifiers.map(mod => {
377
+ if (mod === 'enter')
378
+ return t.binaryExpression('===', t.memberExpression(t.identifier('__ev'), t.identifier('key')), t.stringLiteral('Enter'));
379
+ if (mod === 'escape')
380
+ return t.binaryExpression('===', t.memberExpression(t.identifier('__ev'), t.identifier('key')), t.stringLiteral('Escape'));
381
+ if (mod === 'space')
382
+ return t.binaryExpression('===', t.memberExpression(t.identifier('__ev'), t.identifier('key')), t.stringLiteral(' '));
383
+ return t.booleanLiteral(true);
384
+ });
385
+ const combinedCondition = conditions.reduce((acc, cond) => t.logicalExpression('&&', acc, cond), t.booleanLiteral(true));
386
+ handlerBody = t.ifStatement(combinedCondition, t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier(methodName)), [t.identifier('__ev')])));
387
+ }
388
+ else {
389
+ handlerBody = t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier(methodName)), [t.identifier('__ev')]));
390
+ }
391
+ constructorStatements.push(t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('addEventListener')), [
392
+ t.stringLiteral(baseEvent),
393
+ t.arrowFunctionExpression([t.identifier('__ev')], t.blockStatement([handlerBody]))
394
+ ])));
395
+ }
396
+ if (constructorStatements.length > 0) {
397
+ const constructor = path.node.body.find((m) => t.isClassMethod(m) && m.kind === 'constructor');
398
+ if (constructor) {
399
+ const superIndex = constructor.body.body.findIndex(stmt => t.isExpressionStatement(stmt) && t.isCallExpression(stmt.expression) && t.isSuper(stmt.expression.callee));
400
+ constructor.body.body.splice(superIndex >= 0 ? superIndex + 1 : 0, 0, ...constructorStatements);
401
+ }
402
+ else {
403
+ path.unshiftContainer('body', t.classMethod('constructor', t.identifier('constructor'), [], t.blockStatement([
404
+ t.expressionStatement(t.callExpression(t.super(), [])), ...constructorStatements
405
+ ])));
406
+ }
407
+ }
408
+ }
409
+ }
410
+ };
411
+ }
package/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
package/bin.js ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { Cli, parseArgs } from './Cli.js';
3
+ const { options, args } = parseArgs(process.argv.slice(2));
4
+ const cli = new Cli(options);
5
+ cli.run(args)
6
+ .catch((error) => {
7
+ const message = error instanceof Error ? error.message : String(error);
8
+ console.error(`Error: ${message}`);
9
+ process.exit(1);
10
+ });
@@ -0,0 +1,8 @@
1
+ import type { Plugin } from 'esbuild';
2
+ export interface FluffPluginOptions {
3
+ srcDir: string;
4
+ minify?: boolean;
5
+ sourcemap?: boolean;
6
+ }
7
+ export declare function fluffPlugin(options: FluffPluginOptions): Plugin;
8
+ //# sourceMappingURL=fluff-esbuild-plugin.d.ts.map
@@ -0,0 +1,59 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { ComponentCompiler } from './ComponentCompiler.js';
5
+ function findFluffSourcePath() {
6
+ const thisFile = fileURLToPath(import.meta.url);
7
+ const distDir = path.dirname(thisFile);
8
+ const cliPackageDir = path.dirname(distDir);
9
+ const packagesDir = path.dirname(cliPackageDir);
10
+ const fluffSrcPath = path.join(packagesDir, 'fluff', 'src');
11
+ if (fs.existsSync(path.join(fluffSrcPath, 'index.ts'))) {
12
+ return fluffSrcPath;
13
+ }
14
+ return null;
15
+ }
16
+ export function fluffPlugin(options) {
17
+ const compiler = new ComponentCompiler();
18
+ let componentsDiscovered = false;
19
+ const fluffSrcPath = findFluffSourcePath();
20
+ return {
21
+ name: 'fluff',
22
+ setup(build) {
23
+ build.onStart(async () => {
24
+ if (!componentsDiscovered) {
25
+ await compiler.discoverComponents(options.srcDir);
26
+ componentsDiscovered = true;
27
+ }
28
+ });
29
+ build.onLoad({ filter: /\.component\.ts$/ }, async (args) => {
30
+ const result = await compiler.compileComponentForBundle(args.path, options.minify, options.sourcemap);
31
+ return {
32
+ contents: result.code,
33
+ loader: 'js',
34
+ resolveDir: path.dirname(args.path),
35
+ watchFiles: result.watchFiles
36
+ };
37
+ });
38
+ if (fluffSrcPath) {
39
+ build.onResolve({ filter: /^@fluffjs\/fluff$/ }, () => {
40
+ return { path: path.join(fluffSrcPath, 'index.ts') };
41
+ });
42
+ build.onResolve({ filter: /^@fluffjs\/fluff\// }, (args) => {
43
+ const subPath = args.path.replace('@fluffjs/fluff/', '');
44
+ return { path: path.join(fluffSrcPath, subPath + '.ts') };
45
+ });
46
+ build.onResolve({ filter: /\.js$/ }, (args) => {
47
+ if (args.resolveDir.includes(fluffSrcPath)) {
48
+ const tsPath = path.join(args.resolveDir, args.path.replace('.js', '.ts'));
49
+ if (args.path.includes('types/component')) {
50
+ return { path: args.path, external: true };
51
+ }
52
+ return { path: tsPath };
53
+ }
54
+ return null;
55
+ });
56
+ }
57
+ }
58
+ };
59
+ }
package/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { ComponentCompiler } from './ComponentCompiler.js';
2
+ export { TemplateParser } from './TemplateParser.js';
3
+ export { CodeGenerator } from './CodeGenerator.js';
4
+ export { Cli, parseArgs } from './Cli.js';
5
+ export type * from './types.js';
6
+ export type * from './types/FluffConfig.js';
7
+ //# sourceMappingURL=index.d.ts.map
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { ComponentCompiler } from './ComponentCompiler.js';
2
+ export { TemplateParser } from './TemplateParser.js';
3
+ export { CodeGenerator } from './CodeGenerator.js';
4
+ export { Cli, parseArgs } from './Cli.js';
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@fluffjs/cli",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./index.js",
6
+ "module": "./index.js",
7
+ "types": "./index.d.ts",
8
+ "bin": {
9
+ "fluff": "./bin.js"
10
+ },
11
+ "exports": {
12
+ "./package.json": "./package.json",
13
+ ".": {
14
+ "types": "./index.d.ts",
15
+ "import": "./index.js",
16
+ "default": "./index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "*.js",
21
+ "*.d.ts",
22
+ "!*.tsbuildinfo"
23
+ ],
24
+ "dependencies": {
25
+ "@babel/core": "^7.24.0",
26
+ "@babel/generator": "^7.24.0",
27
+ "@babel/parser": "^7.24.0",
28
+ "@babel/traverse": "^7.24.0",
29
+ "@babel/types": "^7.24.0",
30
+ "esbuild": "^0.20.0",
31
+ "html-minifier-terser": "^7.2.0",
32
+ "parse5": "^7.1.0",
33
+ "source-map": "^0.7.4",
34
+ "tslib": "^2.3.0"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ }
39
+ }
package/types.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ export interface ComponentInfo {
2
+ className: string;
3
+ selector: string;
4
+ templatePath: string;
5
+ stylePath?: string;
6
+ filePath: string;
7
+ }
8
+ export interface TemplateBinding {
9
+ id: string;
10
+ type: 'text' | 'property' | 'event' | 'class' | 'style';
11
+ expression: string;
12
+ target?: string;
13
+ eventName?: string;
14
+ className?: string;
15
+ styleProp?: string;
16
+ }
17
+ export interface SwitchCase {
18
+ value: string | null;
19
+ content: string;
20
+ fallthrough: boolean;
21
+ }
22
+ export interface ControlFlow {
23
+ id: string;
24
+ type: 'if' | 'for' | 'switch';
25
+ condition?: string;
26
+ ifContent?: string;
27
+ elseContent?: string;
28
+ iterator?: string;
29
+ iterable?: string;
30
+ trackBy?: string;
31
+ content?: string;
32
+ expression?: string;
33
+ cases?: SwitchCase[];
34
+ }
35
+ export interface ParsedTemplate {
36
+ html: string;
37
+ bindings: TemplateBinding[];
38
+ controlFlows: ControlFlow[];
39
+ templateRefs: string[];
40
+ }
41
+ export interface CompilerOptions {
42
+ srcDir: string;
43
+ distDir: string;
44
+ componentSelectors: Set<string>;
45
+ }
46
+ //# sourceMappingURL=types.d.ts.map
package/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};