@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.
- package/Cli.d.ts +37 -0
- package/Cli.js +596 -0
- package/CodeGenerator.d.ts +42 -0
- package/CodeGenerator.js +767 -0
- package/ComponentCompiler.d.ts +28 -0
- package/ComponentCompiler.js +354 -0
- package/ControlFlowParser.d.ts +55 -0
- package/ControlFlowParser.js +279 -0
- package/ExpressionTransformer.d.ts +30 -0
- package/ExpressionTransformer.js +276 -0
- package/Generator.d.ts +21 -0
- package/Generator.js +338 -0
- package/IndexHtmlTransformer.d.ts +9 -0
- package/IndexHtmlTransformer.js +97 -0
- package/TemplateParser.d.ts +27 -0
- package/TemplateParser.js +330 -0
- package/babel-plugin-class-transform.d.ts +20 -0
- package/babel-plugin-class-transform.js +40 -0
- package/babel-plugin-component.d.ts +14 -0
- package/babel-plugin-component.js +76 -0
- package/babel-plugin-imports.d.ts +13 -0
- package/babel-plugin-imports.js +76 -0
- package/babel-plugin-reactive.d.ts +22 -0
- package/babel-plugin-reactive.js +411 -0
- package/bin.d.ts +3 -0
- package/bin.js +10 -0
- package/fluff-esbuild-plugin.d.ts +8 -0
- package/fluff-esbuild-plugin.js +59 -0
- package/index.d.ts +7 -0
- package/index.js +4 -0
- package/package.json +39 -0
- package/types.d.ts +46 -0
- package/types.js +1 -0
package/Cli.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
interface CliOptions {
|
|
2
|
+
cwd?: string;
|
|
3
|
+
nxPackage?: string;
|
|
4
|
+
noGzip?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare class Cli {
|
|
7
|
+
private readonly cwd;
|
|
8
|
+
private readonly nxPackage;
|
|
9
|
+
private readonly noGzip;
|
|
10
|
+
constructor(options?: CliOptions);
|
|
11
|
+
run(args: string[]): Promise<void>;
|
|
12
|
+
private showHelp;
|
|
13
|
+
private getProjectRoot;
|
|
14
|
+
private findNxPackageRoot;
|
|
15
|
+
private findNxJson;
|
|
16
|
+
private getConfigPath;
|
|
17
|
+
private isFluffConfig;
|
|
18
|
+
private loadConfig;
|
|
19
|
+
private loadConfigFrom;
|
|
20
|
+
private tryResolveNxProject;
|
|
21
|
+
private init;
|
|
22
|
+
private generate;
|
|
23
|
+
private build;
|
|
24
|
+
private buildTarget;
|
|
25
|
+
private serve;
|
|
26
|
+
private serveTarget;
|
|
27
|
+
private generateEntryPoint;
|
|
28
|
+
private findFiles;
|
|
29
|
+
private matchesPatterns;
|
|
30
|
+
private matchGlob;
|
|
31
|
+
}
|
|
32
|
+
export declare function parseArgs(argv: string[]): {
|
|
33
|
+
options: CliOptions;
|
|
34
|
+
args: string[];
|
|
35
|
+
};
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=Cli.d.ts.map
|
package/Cli.js
ADDED
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
import * as esbuild from 'esbuild';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { gzipSync } from 'zlib';
|
|
5
|
+
import { ComponentCompiler } from './ComponentCompiler.js';
|
|
6
|
+
import { fluffPlugin } from './fluff-esbuild-plugin.js';
|
|
7
|
+
import { transformIndexHtml } from './IndexHtmlTransformer.js';
|
|
8
|
+
import { DEFAULT_CONFIG } from './types/FluffConfig.js';
|
|
9
|
+
import { Generator } from './Generator.js';
|
|
10
|
+
export class Cli {
|
|
11
|
+
cwd;
|
|
12
|
+
nxPackage;
|
|
13
|
+
noGzip;
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.cwd = options.cwd ?? process.env.INIT_CWD ?? process.cwd();
|
|
16
|
+
this.nxPackage = options.nxPackage;
|
|
17
|
+
this.noGzip = options.noGzip ?? false;
|
|
18
|
+
}
|
|
19
|
+
async run(args) {
|
|
20
|
+
const [command, ...commandArgs] = args;
|
|
21
|
+
switch (command) {
|
|
22
|
+
case 'init':
|
|
23
|
+
this.init(commandArgs);
|
|
24
|
+
break;
|
|
25
|
+
case 'build':
|
|
26
|
+
await this.build(commandArgs);
|
|
27
|
+
break;
|
|
28
|
+
case 'generate':
|
|
29
|
+
case 'new':
|
|
30
|
+
this.generate(commandArgs);
|
|
31
|
+
break;
|
|
32
|
+
case 'serve':
|
|
33
|
+
await this.serve(commandArgs);
|
|
34
|
+
break;
|
|
35
|
+
case 'help':
|
|
36
|
+
case '--help':
|
|
37
|
+
case '-h':
|
|
38
|
+
case undefined:
|
|
39
|
+
this.showHelp();
|
|
40
|
+
break;
|
|
41
|
+
default:
|
|
42
|
+
console.error(`Unknown command: ${command}`);
|
|
43
|
+
this.showHelp();
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
showHelp() {
|
|
48
|
+
console.log(`
|
|
49
|
+
Fluff CLI - Build tool for Fluff components
|
|
50
|
+
|
|
51
|
+
Usage: fluff <command> [options]
|
|
52
|
+
|
|
53
|
+
Commands:
|
|
54
|
+
init [target] Initialize a new fluff.json configuration
|
|
55
|
+
generate <app-name> Generate a new Fluff app (alias: new)
|
|
56
|
+
build [target] Build the project (or specific target)
|
|
57
|
+
serve [target] Start dev server with watch mode
|
|
58
|
+
help Show this help message
|
|
59
|
+
|
|
60
|
+
Options:
|
|
61
|
+
--nx <package> Use nx workspace, specify package name
|
|
62
|
+
--cwd <dir> Set working directory
|
|
63
|
+
--no-gzip Disable gzip compression (overrides config)
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
fluff init Create fluff.json with default configuration
|
|
67
|
+
fluff generate my-app Create a new Fluff app called 'my-app'
|
|
68
|
+
fluff build Build the default target
|
|
69
|
+
fluff build app Build the 'app' target
|
|
70
|
+
fluff --nx @myorg/app build Build an nx package
|
|
71
|
+
`);
|
|
72
|
+
}
|
|
73
|
+
getProjectRoot() {
|
|
74
|
+
if (this.nxPackage) {
|
|
75
|
+
return this.findNxPackageRoot(this.nxPackage);
|
|
76
|
+
}
|
|
77
|
+
return this.cwd;
|
|
78
|
+
}
|
|
79
|
+
findNxPackageRoot(packageName) {
|
|
80
|
+
const nxJsonPath = this.findNxJson(this.cwd);
|
|
81
|
+
if (!nxJsonPath) {
|
|
82
|
+
throw new Error('Not in an nx workspace (nx.json not found)');
|
|
83
|
+
}
|
|
84
|
+
const workspaceRoot = path.dirname(nxJsonPath);
|
|
85
|
+
const packagesDir = path.join(workspaceRoot, 'packages');
|
|
86
|
+
const appsDir = path.join(workspaceRoot, 'apps');
|
|
87
|
+
const libsDir = path.join(workspaceRoot, 'libs');
|
|
88
|
+
for (const searchDir of [packagesDir, appsDir, libsDir]) {
|
|
89
|
+
if (!fs.existsSync(searchDir))
|
|
90
|
+
continue;
|
|
91
|
+
const entries = fs.readdirSync(searchDir, { withFileTypes: true });
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
if (!entry.isDirectory())
|
|
94
|
+
continue;
|
|
95
|
+
const pkgJsonPath = path.join(searchDir, entry.name, 'package.json');
|
|
96
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
97
|
+
const pkgJsonContent = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
|
|
98
|
+
if (typeof pkgJsonContent !== 'object' || pkgJsonContent === null)
|
|
99
|
+
continue;
|
|
100
|
+
if ('name' in pkgJsonContent && pkgJsonContent.name === packageName) {
|
|
101
|
+
return path.join(searchDir, entry.name);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
throw new Error(`nx package '${packageName}' not found in workspace`);
|
|
107
|
+
}
|
|
108
|
+
findNxJson(startDir) {
|
|
109
|
+
let dir = startDir;
|
|
110
|
+
while (dir !== path.dirname(dir)) {
|
|
111
|
+
const nxJsonPath = path.join(dir, 'nx.json');
|
|
112
|
+
if (fs.existsSync(nxJsonPath)) {
|
|
113
|
+
return nxJsonPath;
|
|
114
|
+
}
|
|
115
|
+
dir = path.dirname(dir);
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
getConfigPath() {
|
|
120
|
+
return path.join(this.getProjectRoot(), 'fluff.json');
|
|
121
|
+
}
|
|
122
|
+
isFluffConfig(value) {
|
|
123
|
+
if (typeof value !== 'object' || value === null)
|
|
124
|
+
return false;
|
|
125
|
+
if (!('version' in value) || typeof value.version !== 'string')
|
|
126
|
+
return false;
|
|
127
|
+
return !(!('targets' in value) || typeof value.targets !== 'object' || value.targets === null);
|
|
128
|
+
}
|
|
129
|
+
loadConfig() {
|
|
130
|
+
return this.loadConfigFrom(this.getConfigPath());
|
|
131
|
+
}
|
|
132
|
+
loadConfigFrom(configPath) {
|
|
133
|
+
if (!fs.existsSync(configPath)) {
|
|
134
|
+
throw new Error(`fluff.json not found at ${configPath}. Run 'fluff init' first.`);
|
|
135
|
+
}
|
|
136
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
137
|
+
const parsed = JSON.parse(content);
|
|
138
|
+
if (!this.isFluffConfig(parsed)) {
|
|
139
|
+
throw new Error('Invalid fluff.json: missing required fields');
|
|
140
|
+
}
|
|
141
|
+
return parsed;
|
|
142
|
+
}
|
|
143
|
+
tryResolveNxProject(nameOrDir, workspaceRoot) {
|
|
144
|
+
const packagesDir = path.join(workspaceRoot, 'packages');
|
|
145
|
+
const appsDir = path.join(workspaceRoot, 'apps');
|
|
146
|
+
const libsDir = path.join(workspaceRoot, 'libs');
|
|
147
|
+
for (const searchDir of [packagesDir, appsDir, libsDir]) {
|
|
148
|
+
if (!fs.existsSync(searchDir))
|
|
149
|
+
continue;
|
|
150
|
+
const directPath = path.join(searchDir, nameOrDir);
|
|
151
|
+
if (fs.existsSync(path.join(directPath, 'fluff.json'))) {
|
|
152
|
+
return directPath;
|
|
153
|
+
}
|
|
154
|
+
const entries = fs.readdirSync(searchDir, { withFileTypes: true });
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
if (!entry.isDirectory())
|
|
157
|
+
continue;
|
|
158
|
+
const projectPath = path.join(searchDir, entry.name);
|
|
159
|
+
const pkgJsonPath = path.join(projectPath, 'package.json');
|
|
160
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
161
|
+
const pkgJsonContent = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
|
|
162
|
+
if (typeof pkgJsonContent === 'object' && pkgJsonContent !== null && 'name' in pkgJsonContent) {
|
|
163
|
+
const pkgName = pkgJsonContent.name;
|
|
164
|
+
if (pkgName === nameOrDir || pkgName === `@fluffjs/${nameOrDir}`) {
|
|
165
|
+
if (fs.existsSync(path.join(projectPath, 'fluff.json'))) {
|
|
166
|
+
return projectPath;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
init(args) {
|
|
176
|
+
const [targetName] = args;
|
|
177
|
+
const configPath = this.getConfigPath();
|
|
178
|
+
let config = { ...DEFAULT_CONFIG };
|
|
179
|
+
if (fs.existsSync(configPath)) {
|
|
180
|
+
if (targetName) {
|
|
181
|
+
config = this.loadConfig();
|
|
182
|
+
if (config.targets[targetName]) {
|
|
183
|
+
console.error(`Target '${targetName}' already exists in fluff.json`);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
config.targets[targetName] = {
|
|
187
|
+
name: targetName,
|
|
188
|
+
srcDir: `src/${targetName}`,
|
|
189
|
+
outDir: `dist/${targetName}`,
|
|
190
|
+
components: ['**/*.component.ts'],
|
|
191
|
+
assets: ['**/*.html', '**/*.css']
|
|
192
|
+
};
|
|
193
|
+
console.log(`Added target '${targetName}' to fluff.json`);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
console.error('fluff.json already exists. Use "fluff init <target>" to add a new target.');
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
config = { ...DEFAULT_CONFIG };
|
|
202
|
+
if (targetName) {
|
|
203
|
+
config.targets = {
|
|
204
|
+
[targetName]: {
|
|
205
|
+
name: targetName,
|
|
206
|
+
srcDir: 'src',
|
|
207
|
+
outDir: 'dist',
|
|
208
|
+
components: ['**/*.component.ts'],
|
|
209
|
+
assets: ['**/*.html', '**/*.css']
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
config.defaultTarget = targetName;
|
|
213
|
+
}
|
|
214
|
+
console.log('Created fluff.json');
|
|
215
|
+
}
|
|
216
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
217
|
+
console.log(`Configuration saved to ${configPath}`);
|
|
218
|
+
}
|
|
219
|
+
generate(args) {
|
|
220
|
+
const [appName] = args;
|
|
221
|
+
if (!appName) {
|
|
222
|
+
console.error('Usage: fluff generate <app-name>');
|
|
223
|
+
console.error('Example: fluff generate my-app');
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
const generator = new Generator();
|
|
227
|
+
generator.generate({
|
|
228
|
+
appName,
|
|
229
|
+
outputDir: this.cwd
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
async build(args) {
|
|
233
|
+
let targetOrProject = args[0];
|
|
234
|
+
let projectRoot = this.getProjectRoot();
|
|
235
|
+
let workspaceRoot = null;
|
|
236
|
+
let projectRelativePath = null;
|
|
237
|
+
const nxJsonPath = this.findNxJson(this.cwd);
|
|
238
|
+
if (nxJsonPath && targetOrProject) {
|
|
239
|
+
workspaceRoot = path.dirname(nxJsonPath);
|
|
240
|
+
const resolvedProject = this.tryResolveNxProject(targetOrProject, workspaceRoot);
|
|
241
|
+
if (resolvedProject) {
|
|
242
|
+
projectRoot = resolvedProject;
|
|
243
|
+
projectRelativePath = path.relative(workspaceRoot, resolvedProject);
|
|
244
|
+
targetOrProject = undefined;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const configPath = path.join(projectRoot, 'fluff.json');
|
|
248
|
+
if (!fs.existsSync(configPath)) {
|
|
249
|
+
throw new Error(`fluff.json not found at ${configPath}. Run 'fluff init' first.`);
|
|
250
|
+
}
|
|
251
|
+
const config = this.loadConfigFrom(configPath);
|
|
252
|
+
let targets = [];
|
|
253
|
+
if (targetOrProject) {
|
|
254
|
+
const target = config.targets[targetOrProject];
|
|
255
|
+
if (!target) {
|
|
256
|
+
console.error(`Target '${targetOrProject}' not found in fluff.json`);
|
|
257
|
+
console.error(`Available targets: ${Object.keys(config.targets)
|
|
258
|
+
.join(', ')}`);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
targets = [target];
|
|
262
|
+
}
|
|
263
|
+
else if (config.defaultTarget) {
|
|
264
|
+
const target = config.targets[config.defaultTarget];
|
|
265
|
+
if (!target) {
|
|
266
|
+
console.error(`Default target '${config.defaultTarget}' not found in fluff.json`);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
targets = [target];
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
targets = Object.values(config.targets);
|
|
273
|
+
}
|
|
274
|
+
for (const target of targets) {
|
|
275
|
+
await this.buildTarget(target, projectRoot, workspaceRoot, projectRelativePath);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async buildTarget(target, projectRoot, workspaceRoot, projectRelativePath) {
|
|
279
|
+
console.log(`🔨 Building target '${target.name}'...`);
|
|
280
|
+
const srcDir = path.resolve(projectRoot, target.srcDir);
|
|
281
|
+
const appDir = path.join(srcDir, 'app');
|
|
282
|
+
const outDir = (workspaceRoot && projectRelativePath)
|
|
283
|
+
? path.join(workspaceRoot, 'dist', projectRelativePath)
|
|
284
|
+
: path.resolve(projectRoot, target.outDir);
|
|
285
|
+
if (!fs.existsSync(srcDir)) {
|
|
286
|
+
throw new Error(`Source directory not found: ${srcDir}`);
|
|
287
|
+
}
|
|
288
|
+
if (!fs.existsSync(outDir)) {
|
|
289
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
290
|
+
}
|
|
291
|
+
const bundleOptions = { ...target.bundle };
|
|
292
|
+
if (this.noGzip) {
|
|
293
|
+
bundleOptions.gzip = false;
|
|
294
|
+
}
|
|
295
|
+
const entryPoint = target.entryPoint
|
|
296
|
+
? path.join(srcDir, target.entryPoint)
|
|
297
|
+
: await this.generateEntryPoint(srcDir, target.components);
|
|
298
|
+
console.log(' Building with esbuild...');
|
|
299
|
+
const result = await esbuild.build({
|
|
300
|
+
entryPoints: [entryPoint],
|
|
301
|
+
bundle: true,
|
|
302
|
+
outdir: outDir,
|
|
303
|
+
format: 'esm',
|
|
304
|
+
platform: 'browser',
|
|
305
|
+
target: bundleOptions.target ?? 'es2022',
|
|
306
|
+
minify: bundleOptions.minify ?? true,
|
|
307
|
+
splitting: bundleOptions.splitting ?? false,
|
|
308
|
+
treeShaking: true,
|
|
309
|
+
metafile: true,
|
|
310
|
+
plugins: [fluffPlugin({ srcDir: appDir, minify: bundleOptions.minify ?? true })],
|
|
311
|
+
external: bundleOptions.external ?? [],
|
|
312
|
+
logLevel: 'warning',
|
|
313
|
+
tsconfigRaw: '{}'
|
|
314
|
+
});
|
|
315
|
+
const outputs = Object.keys(result.metafile?.outputs ?? {});
|
|
316
|
+
const jsBundle = outputs.find(f => f.endsWith('.js'));
|
|
317
|
+
const cssBundle = outputs.find(f => f.endsWith('.css'));
|
|
318
|
+
if (jsBundle) {
|
|
319
|
+
const jsBundleName = path.basename(jsBundle);
|
|
320
|
+
const jsPath = path.join(outDir, jsBundleName);
|
|
321
|
+
if (bundleOptions.gzip) {
|
|
322
|
+
const jsContent = fs.readFileSync(jsPath);
|
|
323
|
+
const gzipped = gzipSync(jsContent, { level: 9 });
|
|
324
|
+
fs.writeFileSync(`${jsPath}.gz`, gzipped);
|
|
325
|
+
fs.unlinkSync(jsPath);
|
|
326
|
+
console.log(` ✓ Created ${jsBundleName}.gz (${gzipped.length} bytes)`);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
console.log(` ✓ Created ${jsBundleName}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (cssBundle) {
|
|
333
|
+
const cssBundleName = path.basename(cssBundle);
|
|
334
|
+
const cssPath = path.join(outDir, cssBundleName);
|
|
335
|
+
if (bundleOptions.gzip) {
|
|
336
|
+
const cssContent = fs.readFileSync(cssPath);
|
|
337
|
+
const gzipped = gzipSync(cssContent, { level: 9 });
|
|
338
|
+
fs.writeFileSync(`${cssPath}.gz`, gzipped);
|
|
339
|
+
fs.unlinkSync(cssPath);
|
|
340
|
+
console.log(` ✓ Created ${cssBundleName}.gz (${gzipped.length} bytes)`);
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
console.log(` ✓ Created ${cssBundleName}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (target.indexHtml) {
|
|
347
|
+
const indexHtmlPath = path.join(srcDir, target.indexHtml);
|
|
348
|
+
if (fs.existsSync(indexHtmlPath)) {
|
|
349
|
+
const indexHtml = fs.readFileSync(indexHtmlPath, 'utf-8');
|
|
350
|
+
const transformed = await transformIndexHtml(indexHtml, {
|
|
351
|
+
jsBundle: jsBundle ? path.basename(jsBundle) : 'main.js',
|
|
352
|
+
cssBundle: cssBundle ? path.basename(cssBundle) : undefined,
|
|
353
|
+
gzip: bundleOptions.gzip,
|
|
354
|
+
minify: bundleOptions.minify
|
|
355
|
+
});
|
|
356
|
+
fs.writeFileSync(path.join(outDir, 'index.html'), transformed);
|
|
357
|
+
console.log(' ✓ Transformed index.html');
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (target.assets) {
|
|
361
|
+
const compiler = new ComponentCompiler();
|
|
362
|
+
const assetFiles = this.findFiles(srcDir, target.assets);
|
|
363
|
+
for (const filePath of assetFiles) {
|
|
364
|
+
if (filePath.endsWith('.component.ts'))
|
|
365
|
+
continue;
|
|
366
|
+
if (filePath.endsWith('.component.html'))
|
|
367
|
+
continue;
|
|
368
|
+
if (filePath.endsWith('.component.css'))
|
|
369
|
+
continue;
|
|
370
|
+
if (target.indexHtml && filePath.endsWith(target.indexHtml))
|
|
371
|
+
continue;
|
|
372
|
+
const relativePath = path.relative(srcDir, filePath);
|
|
373
|
+
const outPath = path.join(outDir, relativePath);
|
|
374
|
+
const outFileDir = path.dirname(outPath);
|
|
375
|
+
if (!fs.existsSync(outFileDir)) {
|
|
376
|
+
fs.mkdirSync(outFileDir, { recursive: true });
|
|
377
|
+
}
|
|
378
|
+
if (filePath.endsWith('.ts')) {
|
|
379
|
+
let content = fs.readFileSync(filePath, 'utf-8');
|
|
380
|
+
content = await compiler.stripTypeScript(content, filePath);
|
|
381
|
+
fs.writeFileSync(outPath.replace('.ts', '.js'), content);
|
|
382
|
+
console.log(` ✓ Processed ${relativePath}`);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
fs.copyFileSync(filePath, outPath);
|
|
386
|
+
console.log(` ✓ Copied ${relativePath}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
console.log(`✅ Target '${target.name}' built successfully!`);
|
|
391
|
+
}
|
|
392
|
+
async serve(args) {
|
|
393
|
+
let targetOrProject = args[0];
|
|
394
|
+
let projectRoot = this.getProjectRoot();
|
|
395
|
+
let workspaceRoot = null;
|
|
396
|
+
let projectRelativePath = null;
|
|
397
|
+
const nxJsonPath = this.findNxJson(this.cwd);
|
|
398
|
+
if (nxJsonPath && targetOrProject) {
|
|
399
|
+
workspaceRoot = path.dirname(nxJsonPath);
|
|
400
|
+
const resolvedProject = this.tryResolveNxProject(targetOrProject, workspaceRoot);
|
|
401
|
+
if (resolvedProject) {
|
|
402
|
+
projectRoot = resolvedProject;
|
|
403
|
+
projectRelativePath = path.relative(workspaceRoot, resolvedProject);
|
|
404
|
+
targetOrProject = undefined;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const configPath = path.join(projectRoot, 'fluff.json');
|
|
408
|
+
if (!fs.existsSync(configPath)) {
|
|
409
|
+
throw new Error(`fluff.json not found at ${configPath}. Run 'fluff init' first.`);
|
|
410
|
+
}
|
|
411
|
+
const config = this.loadConfigFrom(configPath);
|
|
412
|
+
let target;
|
|
413
|
+
if (targetOrProject) {
|
|
414
|
+
const t = config.targets[targetOrProject];
|
|
415
|
+
if (!t) {
|
|
416
|
+
console.error(`Target '${targetOrProject}' not found in fluff.json`);
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
target = t;
|
|
420
|
+
}
|
|
421
|
+
else if (config.defaultTarget) {
|
|
422
|
+
const t = config.targets[config.defaultTarget];
|
|
423
|
+
if (!t) {
|
|
424
|
+
console.error(`Default target '${config.defaultTarget}' not found in fluff.json`);
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
target = t;
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
target = Object.values(config.targets)[0];
|
|
431
|
+
if (!target) {
|
|
432
|
+
console.error('No targets found in fluff.json');
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
await this.serveTarget(target, projectRoot, workspaceRoot, projectRelativePath);
|
|
437
|
+
}
|
|
438
|
+
async serveTarget(target, projectRoot, workspaceRoot, projectRelativePath) {
|
|
439
|
+
const srcDir = path.resolve(projectRoot, target.srcDir);
|
|
440
|
+
const appDir = path.join(srcDir, 'app');
|
|
441
|
+
const outDir = (workspaceRoot && projectRelativePath)
|
|
442
|
+
? path.join(workspaceRoot, 'dist', projectRelativePath)
|
|
443
|
+
: path.resolve(projectRoot, target.outDir);
|
|
444
|
+
if (!fs.existsSync(outDir)) {
|
|
445
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
446
|
+
}
|
|
447
|
+
const serveOptions = target.serve ?? {};
|
|
448
|
+
const port = serveOptions.port ?? 3000;
|
|
449
|
+
const host = serveOptions.host ?? 'localhost';
|
|
450
|
+
const entryPoint = target.entryPoint
|
|
451
|
+
? path.join(srcDir, target.entryPoint)
|
|
452
|
+
: await this.generateEntryPoint(srcDir, target.components);
|
|
453
|
+
if (target.indexHtml) {
|
|
454
|
+
const indexHtmlPath = path.join(srcDir, target.indexHtml);
|
|
455
|
+
if (fs.existsSync(indexHtmlPath)) {
|
|
456
|
+
const indexHtml = fs.readFileSync(indexHtmlPath, 'utf-8');
|
|
457
|
+
const transformed = await transformIndexHtml(indexHtml, {
|
|
458
|
+
jsBundle: path.basename(entryPoint)
|
|
459
|
+
.replace('.ts', '.js'),
|
|
460
|
+
cssBundle: undefined,
|
|
461
|
+
gzip: false,
|
|
462
|
+
minify: false,
|
|
463
|
+
liveReload: true
|
|
464
|
+
});
|
|
465
|
+
fs.writeFileSync(path.join(outDir, 'index.html'), transformed);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (target.assets) {
|
|
469
|
+
const assetFiles = this.findFiles(srcDir, target.assets);
|
|
470
|
+
for (const filePath of assetFiles) {
|
|
471
|
+
if (filePath.endsWith('.component.ts'))
|
|
472
|
+
continue;
|
|
473
|
+
if (filePath.endsWith('.component.html'))
|
|
474
|
+
continue;
|
|
475
|
+
if (filePath.endsWith('.component.css'))
|
|
476
|
+
continue;
|
|
477
|
+
if (target.indexHtml && filePath.endsWith(target.indexHtml))
|
|
478
|
+
continue;
|
|
479
|
+
if (filePath.endsWith('.ts'))
|
|
480
|
+
continue;
|
|
481
|
+
const relativePath = path.relative(srcDir, filePath);
|
|
482
|
+
const outPath = path.join(outDir, relativePath);
|
|
483
|
+
const outFileDir = path.dirname(outPath);
|
|
484
|
+
if (!fs.existsSync(outFileDir)) {
|
|
485
|
+
fs.mkdirSync(outFileDir, { recursive: true });
|
|
486
|
+
}
|
|
487
|
+
fs.copyFileSync(filePath, outPath);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
console.log(`🚀 Starting dev server for '${target.name}'...`);
|
|
491
|
+
const ctx = await esbuild.context({
|
|
492
|
+
entryPoints: [entryPoint],
|
|
493
|
+
bundle: true,
|
|
494
|
+
outdir: outDir,
|
|
495
|
+
format: 'esm',
|
|
496
|
+
platform: 'browser',
|
|
497
|
+
target: 'es2022',
|
|
498
|
+
minify: false,
|
|
499
|
+
treeShaking: true,
|
|
500
|
+
sourcemap: true,
|
|
501
|
+
plugins: [fluffPlugin({ srcDir: appDir, minify: false, sourcemap: true })],
|
|
502
|
+
logLevel: 'info'
|
|
503
|
+
});
|
|
504
|
+
await ctx.watch();
|
|
505
|
+
console.log(' Watching for changes...');
|
|
506
|
+
const { hosts, port: actualPort } = await ctx.serve({
|
|
507
|
+
servedir: outDir,
|
|
508
|
+
port,
|
|
509
|
+
host
|
|
510
|
+
});
|
|
511
|
+
console.log(` Server running at http://${hosts[0]}:${actualPort}`);
|
|
512
|
+
console.log(' Press Ctrl+C to stop\n');
|
|
513
|
+
}
|
|
514
|
+
async generateEntryPoint(srcDir, componentPatterns) {
|
|
515
|
+
const componentFiles = this.findFiles(srcDir, componentPatterns);
|
|
516
|
+
const imports = componentFiles.map(f => {
|
|
517
|
+
const relativePath = './' + path.relative(srcDir, f)
|
|
518
|
+
.replace(/\\/g, '/');
|
|
519
|
+
return `import '${relativePath}';`;
|
|
520
|
+
});
|
|
521
|
+
const entryContent = imports.join('\n');
|
|
522
|
+
const entryPath = path.join(srcDir, '__generated_entry.ts');
|
|
523
|
+
fs.writeFileSync(entryPath, entryContent);
|
|
524
|
+
return entryPath;
|
|
525
|
+
}
|
|
526
|
+
findFiles(dir, patterns) {
|
|
527
|
+
const files = [];
|
|
528
|
+
const walk = (currentDir) => {
|
|
529
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
530
|
+
for (const entry of entries) {
|
|
531
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
532
|
+
if (entry.isDirectory()) {
|
|
533
|
+
walk(fullPath);
|
|
534
|
+
}
|
|
535
|
+
else if (entry.isFile()) {
|
|
536
|
+
const relativePath = path.relative(dir, fullPath);
|
|
537
|
+
if (this.matchesPatterns(relativePath, patterns)) {
|
|
538
|
+
files.push(fullPath);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
walk(dir);
|
|
544
|
+
return files;
|
|
545
|
+
}
|
|
546
|
+
matchesPatterns(filePath, patterns) {
|
|
547
|
+
for (const pattern of patterns) {
|
|
548
|
+
if (this.matchGlob(filePath, pattern)) {
|
|
549
|
+
return true;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
matchGlob(filePath, pattern) {
|
|
555
|
+
const regexPattern = pattern
|
|
556
|
+
.replace(/\./g, '\\.')
|
|
557
|
+
.replace(/\*\*/g, '{{GLOBSTAR}}')
|
|
558
|
+
.replace(/\*/g, '[^/]*')
|
|
559
|
+
.replace(/\{\{GLOBSTAR}}\//g, '(.*\\/)?')
|
|
560
|
+
.replace(/\{\{GLOBSTAR}}/g, '.*');
|
|
561
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
562
|
+
return regex.test(filePath);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
export function parseArgs(argv) {
|
|
566
|
+
const options = {};
|
|
567
|
+
const args = [];
|
|
568
|
+
let i = 0;
|
|
569
|
+
while (i < argv.length) {
|
|
570
|
+
const arg = argv[i];
|
|
571
|
+
if (arg === '--nx' && argv[i + 1]) {
|
|
572
|
+
options.nxPackage = argv[i + 1];
|
|
573
|
+
i += 2;
|
|
574
|
+
}
|
|
575
|
+
else if (arg === '--cwd' && argv[i + 1]) {
|
|
576
|
+
options.cwd = argv[i + 1];
|
|
577
|
+
i += 2;
|
|
578
|
+
}
|
|
579
|
+
else if (arg === '--no-gzip') {
|
|
580
|
+
options.noGzip = true;
|
|
581
|
+
i++;
|
|
582
|
+
}
|
|
583
|
+
else if (arg?.startsWith('--')) {
|
|
584
|
+
console.error(`Unknown option: ${arg}`);
|
|
585
|
+
process.exit(1);
|
|
586
|
+
}
|
|
587
|
+
else if (arg) {
|
|
588
|
+
args.push(arg);
|
|
589
|
+
i++;
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
i++;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return { options, args };
|
|
596
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ControlFlow, TemplateBinding } from './types.js';
|
|
2
|
+
export declare class CodeGenerator {
|
|
3
|
+
private reactiveProperties;
|
|
4
|
+
private templateRefs;
|
|
5
|
+
setReactiveProperties(props: Set<string>): void;
|
|
6
|
+
setTemplateRefs(refs: string[]): void;
|
|
7
|
+
generateRenderMethod(html: string, styles?: string): string;
|
|
8
|
+
generateBindingsSetup(bindings: TemplateBinding[], controlFlows: ControlFlow[]): string;
|
|
9
|
+
private getPropertyRef;
|
|
10
|
+
private extractBaseProp;
|
|
11
|
+
private escapeForTemplateLiteral;
|
|
12
|
+
private escapeForTemplateLiteralPreservingExpressions;
|
|
13
|
+
private dotsToDashes;
|
|
14
|
+
private removeIndexSuffix;
|
|
15
|
+
private generateBindingCode;
|
|
16
|
+
private generateTextBinding;
|
|
17
|
+
private generatePropertyBinding;
|
|
18
|
+
private generateEventBinding;
|
|
19
|
+
private generateRefLookups;
|
|
20
|
+
private generateClassBinding;
|
|
21
|
+
private generateStyleBinding;
|
|
22
|
+
private generateControlFlowCode;
|
|
23
|
+
private processContentBindings;
|
|
24
|
+
private walkAndTransformBindings;
|
|
25
|
+
private isParse5Element;
|
|
26
|
+
private processAndGenerateBindings;
|
|
27
|
+
private transformInterpolationsInContent;
|
|
28
|
+
private generateBindingSetupCode;
|
|
29
|
+
private extractPropertyBindings;
|
|
30
|
+
private generateUnifiedRender;
|
|
31
|
+
private generateIfCode;
|
|
32
|
+
private generateForCode;
|
|
33
|
+
private generateForBindingSetup;
|
|
34
|
+
private extractEventBindings;
|
|
35
|
+
private walkAndExtractEventBindings;
|
|
36
|
+
private generateSwitchCode;
|
|
37
|
+
private kebabToCamel;
|
|
38
|
+
private extractTextBindings;
|
|
39
|
+
private walkAndExtractTextBindings;
|
|
40
|
+
private walkAndExtractPropertyBindings;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=CodeGenerator.d.ts.map
|