@fluffjs/cli 0.1.8 → 0.1.10

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.d.ts CHANGED
@@ -23,6 +23,7 @@ export declare class Cli {
23
23
  private serve;
24
24
  private serveTarget;
25
25
  private generateEntryPoint;
26
+ private findAllTsFiles;
26
27
  private findFiles;
27
28
  private matchesPatterns;
28
29
  private matchGlob;
package/Cli.js CHANGED
@@ -7,6 +7,7 @@ import picomatch from 'picomatch';
7
7
  import { gzipSync } from 'zlib';
8
8
  import { generate } from './BabelHelpers.js';
9
9
  import { ComponentCompiler } from './ComponentCompiler.js';
10
+ import { DevServer } from './DevServer.js';
10
11
  import { fluffPlugin } from './fluff-esbuild-plugin.js';
11
12
  import { Generator } from './Generator.js';
12
13
  import { IndexHtmlTransformer } from './IndexHtmlTransformer.js';
@@ -195,7 +196,6 @@ Examples:
195
196
  name: targetName,
196
197
  srcDir: `src/${targetName}`,
197
198
  outDir: `dist/${targetName}`,
198
- components: ['**/*.component.ts'],
199
199
  assets: ['**/*.html', '**/*.css']
200
200
  };
201
201
  console.log(`Added target '${targetName}' to fluff.json`);
@@ -213,7 +213,6 @@ Examples:
213
213
  name: targetName,
214
214
  srcDir: 'src',
215
215
  outDir: 'dist',
216
- components: ['**/*.component.ts'],
217
216
  assets: ['**/*.html', '**/*.css']
218
217
  }
219
218
  };
@@ -305,7 +304,7 @@ Examples:
305
304
  }
306
305
  const entryPoint = target.entryPoint
307
306
  ? path.join(srcDir, target.entryPoint)
308
- : this.generateEntryPoint(srcDir, target.components);
307
+ : this.generateEntryPoint(srcDir, target.exclude);
309
308
  let inlineStyles = '';
310
309
  if (target.styles && target.styles.length > 0) {
311
310
  const styleContents = [];
@@ -492,9 +491,21 @@ Examples:
492
491
  const serveOptions = target.serve ?? {};
493
492
  const port = serveOptions.port ?? 3000;
494
493
  const host = serveOptions.host ?? 'localhost';
494
+ const esbuildPort = port + 1;
495
+ let proxyConfig = undefined;
496
+ if (serveOptions.proxyConfig) {
497
+ const proxyConfigPath = path.resolve(projectRoot, serveOptions.proxyConfig);
498
+ proxyConfig = DevServer.loadProxyConfig(proxyConfigPath);
499
+ if (proxyConfig) {
500
+ console.log(` ✓ Proxy config: ${serveOptions.proxyConfig}`);
501
+ for (const route of Object.keys(proxyConfig)) {
502
+ console.log(` ${route} -> ${proxyConfig[route].target}`);
503
+ }
504
+ }
505
+ }
495
506
  const entryPoint = target.entryPoint
496
507
  ? path.join(srcDir, target.entryPoint)
497
- : this.generateEntryPoint(srcDir, target.components);
508
+ : this.generateEntryPoint(srcDir, target.exclude);
498
509
  let inlineStyles = '';
499
510
  if (target.styles && target.styles.length > 0) {
500
511
  const styleContents = [];
@@ -567,20 +578,39 @@ Examples:
567
578
  });
568
579
  await ctx.watch();
569
580
  console.log(' Watching for changes...');
570
- const { hosts, port: actualPort } = await ctx.serve({
571
- servedir: outDir,
572
- port,
573
- host
574
- });
575
- console.log(` Server running at http://${hosts[0]}:${actualPort}`);
576
- console.log(' Press Ctrl+C to stop\n');
581
+ if (proxyConfig) {
582
+ await ctx.serve({
583
+ servedir: outDir,
584
+ port: esbuildPort,
585
+ host: '127.0.0.1'
586
+ });
587
+ const devServer = new DevServer({
588
+ port,
589
+ host,
590
+ esbuildPort,
591
+ esbuildHost: '127.0.0.1',
592
+ proxyConfig
593
+ });
594
+ await devServer.start();
595
+ console.log(` Server running at http://${host}:${port}`);
596
+ console.log(' Press Ctrl+C to stop\n');
597
+ }
598
+ else {
599
+ const { hosts, port: actualPort } = await ctx.serve({
600
+ servedir: outDir,
601
+ port,
602
+ host
603
+ });
604
+ console.log(` Server running at http://${hosts[0]}:${actualPort}`);
605
+ console.log(' Press Ctrl+C to stop\n');
606
+ }
577
607
  }
578
- generateEntryPoint(srcDir, componentPatterns) {
579
- const componentFiles = this.findFiles(srcDir, componentPatterns);
580
- for (const f of componentFiles) {
581
- console.log(` ✓ Component: ${path.relative(srcDir, f)}`);
608
+ generateEntryPoint(srcDir, exclude = []) {
609
+ const tsFiles = this.findAllTsFiles(srcDir, exclude);
610
+ for (const f of tsFiles) {
611
+ console.log(` ✓ ${path.relative(srcDir, f)}`);
582
612
  }
583
- const importDecls = componentFiles.map(f => {
613
+ const importDecls = tsFiles.map(f => {
584
614
  const relativePath = './' + path.relative(srcDir, f)
585
615
  .replace(/\\/g, '/');
586
616
  return t.importDeclaration([], t.stringLiteral(relativePath));
@@ -591,6 +621,28 @@ Examples:
591
621
  fs.writeFileSync(entryPath, entryContent);
592
622
  return entryPath;
593
623
  }
624
+ findAllTsFiles(dir, userExclude = []) {
625
+ const files = [];
626
+ const excludePatterns = ['*.spec.ts', '*.test.ts', '__generated_entry.ts', ...userExclude];
627
+ const walk = (currentDir) => {
628
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
629
+ for (const entry of entries) {
630
+ const fullPath = path.join(currentDir, entry.name);
631
+ if (entry.isDirectory()) {
632
+ walk(fullPath);
633
+ }
634
+ else if (entry.isFile() && entry.name.endsWith('.ts')) {
635
+ const relativePath = path.relative(dir, fullPath).replace(/\\/g, '/');
636
+ const isExcluded = excludePatterns.some(pattern => this.matchGlob(entry.name, pattern) || this.matchGlob(relativePath, pattern));
637
+ if (!isExcluded) {
638
+ files.push(fullPath);
639
+ }
640
+ }
641
+ }
642
+ };
643
+ walk(dir);
644
+ return files;
645
+ }
594
646
  findFiles(dir, patterns) {
595
647
  const files = [];
596
648
  const walk = (currentDir) => {
package/CodeGenerator.js CHANGED
@@ -52,7 +52,7 @@ export class CodeGenerator {
52
52
  const fragment = parse5.parseFragment(html);
53
53
  const styleElement = Parse5Helpers.createElement('style', []);
54
54
  Parse5Helpers.appendText(styleElement, styles);
55
- fragment.childNodes.unshift(styleElement);
55
+ fragment.childNodes.push(styleElement);
56
56
  styleElement.parentNode = fragment;
57
57
  content = parse5.serialize(fragment);
58
58
  }
@@ -51,7 +51,8 @@ export class ComponentCompiler {
51
51
  filename: filePath,
52
52
  presets,
53
53
  plugins,
54
- parserOpts
54
+ parserOpts,
55
+ cwd: path.dirname(new URL(import.meta.url).pathname)
55
56
  });
56
57
  return result?.code ?? code;
57
58
  }
package/DevServer.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ export interface ProxyConfigEntry {
2
+ target: string;
3
+ changeOrigin?: boolean;
4
+ }
5
+ export type ProxyConfig = Record<string, ProxyConfigEntry>;
6
+ export interface DevServerOptions {
7
+ port: number;
8
+ host: string;
9
+ esbuildPort: number;
10
+ esbuildHost: string;
11
+ proxyConfig?: ProxyConfig;
12
+ }
13
+ export declare class DevServer {
14
+ private readonly options;
15
+ private readonly proxy;
16
+ private server;
17
+ constructor(options: DevServerOptions);
18
+ static loadProxyConfig(configPath: string): ProxyConfig | undefined;
19
+ private static isProxyEntry;
20
+ start(): Promise<number>;
21
+ stop(): void;
22
+ private handleRequest;
23
+ private findProxyEntry;
24
+ private proxyRequest;
25
+ private forwardToEsbuild;
26
+ }
27
+ //# sourceMappingURL=DevServer.d.ts.map
package/DevServer.js ADDED
@@ -0,0 +1,109 @@
1
+ import * as http from 'node:http';
2
+ import * as fs from 'node:fs';
3
+ import httpProxy from 'http-proxy';
4
+ export class DevServer {
5
+ options;
6
+ proxy;
7
+ server = null;
8
+ constructor(options) {
9
+ this.options = options;
10
+ this.proxy = httpProxy.createProxyServer({});
11
+ this.proxy.on('error', (err, req, res) => {
12
+ console.error('Proxy error:', err.message);
13
+ if (res instanceof http.ServerResponse && !res.headersSent) {
14
+ res.writeHead(502, { 'Content-Type': 'text/plain' });
15
+ res.end('Proxy error: ' + err.message);
16
+ }
17
+ });
18
+ }
19
+ static loadProxyConfig(configPath) {
20
+ if (!fs.existsSync(configPath)) {
21
+ console.warn(`Proxy config not found: ${configPath}`);
22
+ return undefined;
23
+ }
24
+ try {
25
+ const content = fs.readFileSync(configPath, 'utf-8');
26
+ const config = JSON.parse(content);
27
+ if (typeof config === 'object' && config !== null && !Array.isArray(config)) {
28
+ const result = {};
29
+ for (const [key, value] of Object.entries(config)) {
30
+ if (DevServer.isProxyEntry(value)) {
31
+ result[key] = {
32
+ target: value.target,
33
+ changeOrigin: typeof value.changeOrigin === 'boolean' ? value.changeOrigin : undefined
34
+ };
35
+ }
36
+ }
37
+ return result;
38
+ }
39
+ return undefined;
40
+ }
41
+ catch (err) {
42
+ console.error('Failed to parse proxy config:', err);
43
+ return undefined;
44
+ }
45
+ }
46
+ static isProxyEntry(value) {
47
+ return typeof value === 'object' &&
48
+ value !== null &&
49
+ 'target' in value &&
50
+ typeof value.target === 'string';
51
+ }
52
+ async start() {
53
+ return new Promise((resolve, reject) => {
54
+ this.server = http.createServer((req, res) => {
55
+ this.handleRequest(req, res);
56
+ });
57
+ this.server.on('error', (err) => {
58
+ reject(err);
59
+ });
60
+ this.server.listen(this.options.port, () => {
61
+ const address = this.server?.address();
62
+ const port = typeof address === 'object' && address ? address.port : this.options.port;
63
+ resolve(port);
64
+ });
65
+ });
66
+ }
67
+ stop() {
68
+ if (this.server) {
69
+ this.server.close();
70
+ this.server = null;
71
+ }
72
+ this.proxy.close();
73
+ }
74
+ handleRequest(req, res) {
75
+ const url = req.url ?? '/';
76
+ const [pathname] = url.split('?');
77
+ const proxyEntry = this.findProxyEntry(pathname);
78
+ if (proxyEntry) {
79
+ this.proxyRequest(req, res, proxyEntry);
80
+ }
81
+ else {
82
+ this.forwardToEsbuild(req, res);
83
+ }
84
+ }
85
+ findProxyEntry(pathname) {
86
+ if (!this.options.proxyConfig) {
87
+ return undefined;
88
+ }
89
+ for (const [pattern, entry] of Object.entries(this.options.proxyConfig)) {
90
+ if (pathname === pattern || pathname.startsWith(pattern + '/') || pathname.startsWith(pattern + '?')) {
91
+ return entry;
92
+ }
93
+ }
94
+ return undefined;
95
+ }
96
+ proxyRequest(req, res, entry) {
97
+ const options = {
98
+ target: entry.target,
99
+ changeOrigin: entry.changeOrigin ?? false
100
+ };
101
+ this.proxy.web(req, res, options);
102
+ }
103
+ forwardToEsbuild(req, res) {
104
+ const options = {
105
+ target: `http://${this.options.esbuildHost}:${this.options.esbuildPort}`
106
+ };
107
+ this.proxy.web(req, res, options);
108
+ }
109
+ }
@@ -287,7 +287,7 @@ export default function reactivePlugin() {
287
287
  constructorStatements.push(t.expressionStatement(t.unaryExpression('void', t.memberExpression(t.thisExpression(), t.identifier(methodName)))));
288
288
  for (const prop of mProps) {
289
289
  if (reactiveProps.has(prop)) {
290
- 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)), []))])));
290
+ constructorStatements.push(t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier('__baseSubscriptions')), t.identifier('push')), [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)), [t.stringLiteral(prop)]))])])));
291
291
  }
292
292
  }
293
293
  }
@@ -299,7 +299,7 @@ export default function reactivePlugin() {
299
299
  for (const prop of wProps) {
300
300
  if (reactiveProps.has(prop)) {
301
301
  iife.push(t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('__subs'), t.identifier('push')), [
302
- t.callExpression(t.memberExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(`__${prop}`)), t.identifier('onChange')), t.identifier('subscribe')), [t.identifier('__cb')])
302
+ t.callExpression(t.memberExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(`__${prop}`)), t.identifier('onChange')), t.identifier('subscribe')), [t.arrowFunctionExpression([], t.callExpression(t.identifier('__cb'), [t.stringLiteral(prop)]))])
303
303
  ])));
304
304
  }
305
305
  }
@@ -7,8 +7,8 @@ export interface FluffTarget {
7
7
  componentsDir?: string;
8
8
  tsConfigPath?: string;
9
9
  entryPoint?: string;
10
+ exclude?: string[];
10
11
  indexHtml?: string;
11
- components: string[];
12
12
  styles?: string[];
13
13
  assets?: string[];
14
14
  bundle?: BundleOptions;
@@ -1,5 +1,6 @@
1
1
  export interface ServeOptions {
2
2
  port?: number;
3
3
  host?: string;
4
+ proxyConfig?: string;
4
5
  }
5
6
  //# sourceMappingURL=ServeOptions.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluffjs/cli",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -32,6 +32,7 @@
32
32
  "esbuild": "^0.27.2",
33
33
  "he": "^1.2.0",
34
34
  "html-minifier-terser": "^7.2.0",
35
+ "http-proxy": "^1.18.1",
35
36
  "picomatch": "^4.0.2",
36
37
  "parse5": "^8.0.0",
37
38
  "parse5-sax-parser": "^8.0.0",
@@ -40,6 +41,7 @@
40
41
  },
41
42
  "devDependencies": {
42
43
  "@types/he": "^1.2.3",
44
+ "@types/http-proxy": "^1.17.16",
43
45
  "@types/jsesc": "^3.0.3",
44
46
  "jsesc": "^3.1.0"
45
47
  },
@@ -8,7 +8,6 @@ export const DEFAULT_CONFIG = {
8
8
  componentsDir: 'app',
9
9
  entryPoint: 'main.ts',
10
10
  indexHtml: 'index.html',
11
- components: ['**/*.component.ts'],
12
11
  assets: ['**/*.html', '**/*.css'],
13
12
  bundle: {
14
13
  minify: true,