@fluffjs/cli 0.2.4 → 0.3.0
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 +2 -0
- package/Cli.js +40 -1
- package/CodeGenerator.d.ts +7 -2
- package/CodeGenerator.js +82 -12
- package/ComponentCompiler.d.ts +2 -2
- package/ComponentCompiler.js +18 -18
- package/babel-plugin-reactive.js +117 -32
- package/fluff-esbuild-plugin.js +125 -10
- package/interfaces/BabelPluginReactiveState.d.ts +1 -0
- package/package.json +1 -1
- package/testing/MarkerConfigAstReader.d.ts +22 -0
- package/testing/MarkerConfigAstReader.js +162 -0
package/Cli.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare class Cli {
|
|
|
6
6
|
private readonly noMinify;
|
|
7
7
|
private readonly gzScriptTag;
|
|
8
8
|
constructor(options?: CliOptions);
|
|
9
|
+
private resolveCwd;
|
|
9
10
|
run(args: string[]): Promise<void>;
|
|
10
11
|
private showHelp;
|
|
11
12
|
private getProjectRoot;
|
|
@@ -16,6 +17,7 @@ export declare class Cli {
|
|
|
16
17
|
private loadConfig;
|
|
17
18
|
private loadConfigFrom;
|
|
18
19
|
private tryResolveNxProject;
|
|
20
|
+
private findProjectJsonFiles;
|
|
19
21
|
private init;
|
|
20
22
|
private generate;
|
|
21
23
|
private build;
|
package/Cli.js
CHANGED
|
@@ -20,12 +20,19 @@ export class Cli {
|
|
|
20
20
|
noMinify;
|
|
21
21
|
gzScriptTag;
|
|
22
22
|
constructor(options = {}) {
|
|
23
|
-
this.cwd = options.cwd ??
|
|
23
|
+
this.cwd = options.cwd ?? this.resolveCwd();
|
|
24
24
|
this.nxPackage = options.nxPackage;
|
|
25
25
|
this.noGzip = options.noGzip ?? false;
|
|
26
26
|
this.noMinify = options.noMinify ?? false;
|
|
27
27
|
this.gzScriptTag = options.gzScriptTag ?? false;
|
|
28
28
|
}
|
|
29
|
+
resolveCwd() {
|
|
30
|
+
const processCwd = process.cwd();
|
|
31
|
+
if (fs.existsSync(path.join(processCwd, 'fluff.json'))) {
|
|
32
|
+
return processCwd;
|
|
33
|
+
}
|
|
34
|
+
return process.env.INIT_CWD ?? processCwd;
|
|
35
|
+
}
|
|
29
36
|
async run(args) {
|
|
30
37
|
const [command, ...commandArgs] = args;
|
|
31
38
|
switch (command) {
|
|
@@ -151,6 +158,17 @@ Examples:
|
|
|
151
158
|
return parsed;
|
|
152
159
|
}
|
|
153
160
|
tryResolveNxProject(nameOrDir, workspaceRoot) {
|
|
161
|
+
const projectJsonPaths = this.findProjectJsonFiles(workspaceRoot);
|
|
162
|
+
for (const projectJsonPath of projectJsonPaths) {
|
|
163
|
+
const projectDir = path.dirname(projectJsonPath);
|
|
164
|
+
const projectJsonContent = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));
|
|
165
|
+
if (typeof projectJsonContent === 'object' && projectJsonContent !== null && 'name' in projectJsonContent) {
|
|
166
|
+
const projectName = projectJsonContent.name;
|
|
167
|
+
if (projectName === nameOrDir && fs.existsSync(path.join(projectDir, 'fluff.json'))) {
|
|
168
|
+
return projectDir;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
154
172
|
const packagesDir = path.join(workspaceRoot, 'packages');
|
|
155
173
|
const appsDir = path.join(workspaceRoot, 'apps');
|
|
156
174
|
const libsDir = path.join(workspaceRoot, 'libs');
|
|
@@ -182,6 +200,26 @@ Examples:
|
|
|
182
200
|
}
|
|
183
201
|
return null;
|
|
184
202
|
}
|
|
203
|
+
findProjectJsonFiles(dir, depth = 0) {
|
|
204
|
+
if (depth > 3)
|
|
205
|
+
return [];
|
|
206
|
+
const results = [];
|
|
207
|
+
const projectJsonPath = path.join(dir, 'project.json');
|
|
208
|
+
if (fs.existsSync(projectJsonPath)) {
|
|
209
|
+
results.push(projectJsonPath);
|
|
210
|
+
}
|
|
211
|
+
if (depth < 3) {
|
|
212
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
213
|
+
for (const entry of entries) {
|
|
214
|
+
if (!entry.isDirectory())
|
|
215
|
+
continue;
|
|
216
|
+
if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name.startsWith('.'))
|
|
217
|
+
continue;
|
|
218
|
+
results.push(...this.findProjectJsonFiles(path.join(dir, entry.name), depth + 1));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return results;
|
|
222
|
+
}
|
|
185
223
|
init(args) {
|
|
186
224
|
const [targetName] = args;
|
|
187
225
|
const configPath = this.getConfigPath();
|
|
@@ -645,6 +683,7 @@ Examples:
|
|
|
645
683
|
.replace(/\\/g, '/');
|
|
646
684
|
return t.importDeclaration([], t.stringLiteral(relativePath));
|
|
647
685
|
});
|
|
686
|
+
importDecls.unshift(t.importDeclaration([], t.stringLiteral('@fluff/expr-table')));
|
|
648
687
|
const program = t.program(importDecls);
|
|
649
688
|
const entryContent = generate(program, { compact: false }).code;
|
|
650
689
|
return { contents: entryContent, resolveDir: srcDir };
|
package/CodeGenerator.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as t from '@babel/types';
|
|
1
2
|
import type { BreakMarkerConfig } from './interfaces/BreakMarkerConfig.js';
|
|
2
3
|
import type { ForMarkerConfig } from './interfaces/ForMarkerConfig.js';
|
|
3
4
|
import type { IfMarkerConfig } from './interfaces/IfMarkerConfig.js';
|
|
@@ -28,8 +29,12 @@ export declare class CodeGenerator {
|
|
|
28
29
|
static resetGlobalState(): void;
|
|
29
30
|
generateRenderMethod(template: ParsedTemplate, styles?: string): string;
|
|
30
31
|
generateHtml(template: ParsedTemplate): string;
|
|
31
|
-
generateRenderMethodFromHtml(html: string, styles?: string,
|
|
32
|
-
|
|
32
|
+
generateRenderMethodFromHtml(html: string, styles?: string, markerConfigExpr?: t.Expression): string;
|
|
33
|
+
getMarkerConfigExpression(): t.Expression;
|
|
34
|
+
private buildMarkerConfigExpression;
|
|
35
|
+
private buildMarkerConfigObject;
|
|
36
|
+
private buildDepsExpression;
|
|
37
|
+
private buildPropertyChainExpression;
|
|
33
38
|
generateBindingsSetup(): string;
|
|
34
39
|
getBindingsMap(): Record<string, Record<string, unknown>[]>;
|
|
35
40
|
generateExpressionAssignments(): string;
|
package/CodeGenerator.js
CHANGED
|
@@ -33,8 +33,8 @@ export class CodeGenerator {
|
|
|
33
33
|
this.markerId = 0;
|
|
34
34
|
this.markerConfigs.clear();
|
|
35
35
|
const html = this.generateHtml(template);
|
|
36
|
-
const
|
|
37
|
-
return this.generateRenderMethodFromHtml(html, styles,
|
|
36
|
+
const markerConfigExpr = this.getMarkerConfigExpression();
|
|
37
|
+
return this.generateRenderMethodFromHtml(html, styles, markerConfigExpr);
|
|
38
38
|
}
|
|
39
39
|
generateHtml(template) {
|
|
40
40
|
this.rootFragment = parse5.parseFragment('');
|
|
@@ -46,7 +46,7 @@ export class CodeGenerator {
|
|
|
46
46
|
}
|
|
47
47
|
return parse5.serialize(this.rootFragment);
|
|
48
48
|
}
|
|
49
|
-
generateRenderMethodFromHtml(html, styles,
|
|
49
|
+
generateRenderMethodFromHtml(html, styles, markerConfigExpr) {
|
|
50
50
|
let content = html;
|
|
51
51
|
if (styles) {
|
|
52
52
|
const fragment = parse5.parseFragment(html);
|
|
@@ -58,14 +58,86 @@ export class CodeGenerator {
|
|
|
58
58
|
}
|
|
59
59
|
const statements = [];
|
|
60
60
|
statements.push(t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__getShadowRoot')), []), t.identifier('innerHTML')), t.stringLiteral(content))));
|
|
61
|
-
if (
|
|
62
|
-
statements.push(t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__setMarkerConfigs')), [
|
|
61
|
+
if (markerConfigExpr) {
|
|
62
|
+
statements.push(t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__setMarkerConfigs')), [markerConfigExpr])));
|
|
63
63
|
}
|
|
64
64
|
const program = t.program(statements);
|
|
65
65
|
return generate(program, { compact: false }).code;
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
return
|
|
67
|
+
getMarkerConfigExpression() {
|
|
68
|
+
return this.buildMarkerConfigExpression();
|
|
69
|
+
}
|
|
70
|
+
buildMarkerConfigExpression() {
|
|
71
|
+
const entries = Array.from(this.markerConfigs.entries())
|
|
72
|
+
.map(([id, config]) => t.arrayExpression([
|
|
73
|
+
t.numericLiteral(id),
|
|
74
|
+
this.buildMarkerConfigObject(config)
|
|
75
|
+
]));
|
|
76
|
+
return t.arrayExpression(entries);
|
|
77
|
+
}
|
|
78
|
+
buildMarkerConfigObject(config) {
|
|
79
|
+
const properties = [
|
|
80
|
+
t.objectProperty(t.stringLiteral('type'), t.stringLiteral(config.type))
|
|
81
|
+
];
|
|
82
|
+
if (config.type === 'text') {
|
|
83
|
+
properties.push(t.objectProperty(t.stringLiteral('exprId'), t.numericLiteral(config.exprId)));
|
|
84
|
+
if (config.deps) {
|
|
85
|
+
properties.push(t.objectProperty(t.stringLiteral('deps'), this.buildDepsExpression(config.deps)));
|
|
86
|
+
}
|
|
87
|
+
if (config.pipes && config.pipes.length > 0) {
|
|
88
|
+
properties.push(t.objectProperty(t.stringLiteral('pipes'), t.arrayExpression(config.pipes.map(pipe => t.objectExpression([
|
|
89
|
+
t.objectProperty(t.stringLiteral('name'), t.stringLiteral(pipe.name)),
|
|
90
|
+
t.objectProperty(t.stringLiteral('argExprIds'), t.arrayExpression(pipe.argExprIds.map(arg => t.numericLiteral(arg))))
|
|
91
|
+
])))));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (config.type === 'if') {
|
|
95
|
+
properties.push(t.objectProperty(t.stringLiteral('branches'), t.arrayExpression(config.branches.map(branch => {
|
|
96
|
+
const branchProps = [];
|
|
97
|
+
if (branch.exprId !== undefined) {
|
|
98
|
+
branchProps.push(t.objectProperty(t.stringLiteral('exprId'), t.numericLiteral(branch.exprId)));
|
|
99
|
+
}
|
|
100
|
+
if (branch.deps) {
|
|
101
|
+
branchProps.push(t.objectProperty(t.stringLiteral('deps'), this.buildDepsExpression(branch.deps)));
|
|
102
|
+
}
|
|
103
|
+
return t.objectExpression(branchProps);
|
|
104
|
+
}))));
|
|
105
|
+
}
|
|
106
|
+
else if (config.type === 'for') {
|
|
107
|
+
properties.push(t.objectProperty(t.stringLiteral('iterator'), t.stringLiteral(config.iterator)), t.objectProperty(t.stringLiteral('iterableExprId'), t.numericLiteral(config.iterableExprId)), t.objectProperty(t.stringLiteral('hasEmpty'), t.booleanLiteral(config.hasEmpty)));
|
|
108
|
+
if (config.deps) {
|
|
109
|
+
properties.push(t.objectProperty(t.stringLiteral('deps'), this.buildDepsExpression(config.deps)));
|
|
110
|
+
}
|
|
111
|
+
if (config.trackBy !== undefined) {
|
|
112
|
+
properties.push(t.objectProperty(t.stringLiteral('trackBy'), t.stringLiteral(config.trackBy)));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (config.type === 'switch') {
|
|
116
|
+
properties.push(t.objectProperty(t.stringLiteral('expressionExprId'), t.numericLiteral(config.expressionExprId)));
|
|
117
|
+
if (config.deps) {
|
|
118
|
+
properties.push(t.objectProperty(t.stringLiteral('deps'), this.buildDepsExpression(config.deps)));
|
|
119
|
+
}
|
|
120
|
+
properties.push(t.objectProperty(t.stringLiteral('cases'), t.arrayExpression(config.cases.map(caseConfig => {
|
|
121
|
+
const caseProps = [
|
|
122
|
+
t.objectProperty(t.stringLiteral('isDefault'), t.booleanLiteral(caseConfig.isDefault)),
|
|
123
|
+
t.objectProperty(t.stringLiteral('fallthrough'), t.booleanLiteral(caseConfig.fallthrough))
|
|
124
|
+
];
|
|
125
|
+
if (caseConfig.valueExprId !== undefined) {
|
|
126
|
+
caseProps.push(t.objectProperty(t.stringLiteral('valueExprId'), t.numericLiteral(caseConfig.valueExprId)));
|
|
127
|
+
}
|
|
128
|
+
return t.objectExpression(caseProps);
|
|
129
|
+
}))));
|
|
130
|
+
}
|
|
131
|
+
return t.objectExpression(properties);
|
|
132
|
+
}
|
|
133
|
+
buildDepsExpression(deps) {
|
|
134
|
+
return t.arrayExpression(deps.map(dep => this.buildPropertyChainExpression(dep)));
|
|
135
|
+
}
|
|
136
|
+
buildPropertyChainExpression(dep) {
|
|
137
|
+
if (Array.isArray(dep)) {
|
|
138
|
+
return t.arrayExpression(dep.map(part => t.stringLiteral(part)));
|
|
139
|
+
}
|
|
140
|
+
return t.stringLiteral(dep);
|
|
69
141
|
}
|
|
70
142
|
generateBindingsSetup() {
|
|
71
143
|
const statements = [
|
|
@@ -107,11 +179,9 @@ export class CodeGenerator {
|
|
|
107
179
|
const normalizedHandler = CodeGenerator.normalizeCompiledExpr(h);
|
|
108
180
|
return CodeGenerator.buildHandlerArrowFunction(['t', 'l', '__ev'], normalizedHandler);
|
|
109
181
|
});
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
];
|
|
114
|
-
const program = t.program(statements);
|
|
182
|
+
const fluffBaseImport = t.importDeclaration([t.importSpecifier(t.identifier('FluffBase'), t.identifier('FluffBase'))], t.stringLiteral('@fluffjs/fluff'));
|
|
183
|
+
const setExprTableCall = t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('FluffBase'), t.identifier('__setExpressionTable')), [t.arrayExpression(exprElements), t.arrayExpression(handlerElements)]));
|
|
184
|
+
const program = t.program([fluffBaseImport, setExprTableCall]);
|
|
115
185
|
return generate(program, { compact: false }).code;
|
|
116
186
|
}
|
|
117
187
|
static buildExpressionArrowFunction(params, bodyExpr) {
|
package/ComponentCompiler.d.ts
CHANGED
|
@@ -8,18 +8,18 @@ export declare class ComponentCompiler {
|
|
|
8
8
|
private getReactivePropsForFile;
|
|
9
9
|
protected createTemplateParser(_filePath: string): TemplateParser;
|
|
10
10
|
private runBabelTransform;
|
|
11
|
-
discoverComponents(dir: string): Promise<
|
|
11
|
+
discoverComponents(dir: string): Promise<string[]>;
|
|
12
12
|
compileComponentForBundle(filePath: string, minify?: boolean, sourcemap?: boolean, skipDefine?: boolean, production?: boolean): Promise<CompileResult>;
|
|
13
13
|
stripTypeScriptWithSourceMap(code: string, filePath: string, sourcemap?: boolean): Promise<CompileResult>;
|
|
14
14
|
transformImportsForBundle(code: string, filePath: string): Promise<string>;
|
|
15
15
|
transformReactiveProperties(code: string, filePath?: string, production?: boolean): Promise<string>;
|
|
16
|
+
transformPipeDecorators(code: string, filePath?: string): Promise<string>;
|
|
16
17
|
stripTypeScript(code: string, filePath?: string): Promise<string>;
|
|
17
18
|
extractComponentMetadata(code: string, filePath: string): Promise<ComponentMetadata | null>;
|
|
18
19
|
transformClass(code: string, filePath: string, options: ClassTransformOptions): Promise<string>;
|
|
19
20
|
transformLibraryImports(code: string, filePath: string): Promise<string>;
|
|
20
21
|
private createComponentSourceMap;
|
|
21
22
|
private addFluffImport;
|
|
22
|
-
private appendCode;
|
|
23
23
|
private addBindingsMap;
|
|
24
24
|
private addCustomElementsDefine;
|
|
25
25
|
}
|
package/ComponentCompiler.js
CHANGED
|
@@ -62,20 +62,24 @@ export class ComponentCompiler {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
async discoverComponents(dir) {
|
|
65
|
+
const componentPaths = [];
|
|
65
66
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
66
67
|
for (const entry of entries) {
|
|
67
68
|
const fullPath = path.join(dir, entry.name);
|
|
68
69
|
if (entry.isDirectory()) {
|
|
69
|
-
await this.discoverComponents(fullPath);
|
|
70
|
+
const subPaths = await this.discoverComponents(fullPath);
|
|
71
|
+
componentPaths.push(...subPaths);
|
|
70
72
|
}
|
|
71
|
-
else if (entry.name.endsWith('.
|
|
73
|
+
else if (entry.name.endsWith('.ts')) {
|
|
72
74
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
73
75
|
const metadata = await this.extractComponentMetadata(content, fullPath);
|
|
74
76
|
if (metadata?.selector) {
|
|
75
77
|
this.componentSelectors.add(metadata.selector);
|
|
78
|
+
componentPaths.push(fullPath);
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
81
|
}
|
|
82
|
+
return componentPaths;
|
|
79
83
|
}
|
|
80
84
|
async compileComponentForBundle(filePath, minify, sourcemap, skipDefine, production) {
|
|
81
85
|
let source = fs.readFileSync(filePath, 'utf-8');
|
|
@@ -134,20 +138,15 @@ export class ComponentCompiler {
|
|
|
134
138
|
removeEmptyAttributes: true
|
|
135
139
|
});
|
|
136
140
|
}
|
|
137
|
-
const
|
|
138
|
-
const renderMethod = gen.generateRenderMethodFromHtml(generatedHtml, styles,
|
|
139
|
-
const bindingsSetup = gen.generateBindingsSetup();
|
|
141
|
+
const markerConfigExpr = gen.getMarkerConfigExpression();
|
|
142
|
+
const renderMethod = gen.generateRenderMethodFromHtml(generatedHtml, styles, markerConfigExpr);
|
|
140
143
|
let result = await this.transformImportsForBundle(source, filePath);
|
|
141
144
|
result = await this.transformClass(result, filePath, {
|
|
142
145
|
className, originalSuperClass: 'HTMLElement', newSuperClass: 'FluffElement', injectMethods: [
|
|
143
|
-
{ name: '__render', body: renderMethod }
|
|
146
|
+
{ name: '__render', body: renderMethod }
|
|
144
147
|
]
|
|
145
148
|
});
|
|
146
149
|
result = this.addFluffImport(result);
|
|
147
|
-
const exprAssignments = gen.generateExpressionAssignments();
|
|
148
|
-
if (exprAssignments) {
|
|
149
|
-
result = this.appendCode(result, exprAssignments);
|
|
150
|
-
}
|
|
151
150
|
const bindingsMap = gen.getBindingsMap();
|
|
152
151
|
if (Object.keys(bindingsMap).length > 0) {
|
|
153
152
|
result = this.addBindingsMap(result, className, bindingsMap);
|
|
@@ -207,6 +206,14 @@ export class ComponentCompiler {
|
|
|
207
206
|
errorContext: 'Babel transform error'
|
|
208
207
|
});
|
|
209
208
|
}
|
|
209
|
+
async transformPipeDecorators(code, filePath = 'file.ts') {
|
|
210
|
+
return this.runBabelTransform(code, filePath, {
|
|
211
|
+
useTypeScriptPreset: true,
|
|
212
|
+
useDecoratorSyntax: true,
|
|
213
|
+
plugins: [reactivePlugin],
|
|
214
|
+
errorContext: 'Pipe decorator transform error'
|
|
215
|
+
});
|
|
216
|
+
}
|
|
210
217
|
async stripTypeScript(code, filePath = 'file.ts') {
|
|
211
218
|
try {
|
|
212
219
|
const result = await esbuild.transform(code, {
|
|
@@ -284,19 +291,12 @@ export class ComponentCompiler {
|
|
|
284
291
|
const ast = parse(code, { sourceType: 'module' });
|
|
285
292
|
const importSpecifiers = [
|
|
286
293
|
t.importSpecifier(t.identifier('FluffBase'), t.identifier('FluffBase')),
|
|
287
|
-
t.importSpecifier(t.identifier('FluffElement'), t.identifier('FluffElement'))
|
|
288
|
-
t.importSpecifier(t.identifier('MarkerManager'), t.identifier('MarkerManager'))
|
|
294
|
+
t.importSpecifier(t.identifier('FluffElement'), t.identifier('FluffElement'))
|
|
289
295
|
];
|
|
290
296
|
const importDecl = t.importDeclaration(importSpecifiers, t.stringLiteral('@fluffjs/fluff'));
|
|
291
297
|
ast.program.body.unshift(importDecl);
|
|
292
298
|
return generate(ast, { compact: false }).code;
|
|
293
299
|
}
|
|
294
|
-
appendCode(code, additionalCode) {
|
|
295
|
-
const ast = parse(code, { sourceType: 'module' });
|
|
296
|
-
const additionalAst = parse(additionalCode, { sourceType: 'module' });
|
|
297
|
-
ast.program.body.push(...additionalAst.program.body);
|
|
298
|
-
return generate(ast, { compact: false }).code;
|
|
299
|
-
}
|
|
300
300
|
addBindingsMap(code, className, bindingsMap) {
|
|
301
301
|
const ast = parse(code, { sourceType: 'module' });
|
|
302
302
|
const jsonStr = JSON.stringify(bindingsMap);
|
package/babel-plugin-reactive.js
CHANGED
|
@@ -8,6 +8,7 @@ export default function reactivePlugin() {
|
|
|
8
8
|
Program: {
|
|
9
9
|
enter(path, state) {
|
|
10
10
|
state.needsPropertyImport = false;
|
|
11
|
+
state.needsPipeRegistryImport = false;
|
|
11
12
|
state.reactiveProperties = new Set();
|
|
12
13
|
state.watchMethods = [];
|
|
13
14
|
}, exit(path, state) {
|
|
@@ -27,6 +28,18 @@ export default function reactivePlugin() {
|
|
|
27
28
|
path.node.body.unshift(importDecl);
|
|
28
29
|
}
|
|
29
30
|
}
|
|
31
|
+
if (state.needsPipeRegistryImport) {
|
|
32
|
+
const hasPipeRegistryImport = path.node.body.some(node => {
|
|
33
|
+
if (t.isImportDeclaration(node)) {
|
|
34
|
+
return node.specifiers.some(spec => t.isImportSpecifier(spec) && t.isIdentifier(spec.imported) && spec.imported.name === 'pipeRegistry');
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
});
|
|
38
|
+
if (!hasPipeRegistryImport) {
|
|
39
|
+
const importDecl = t.importDeclaration([t.importSpecifier(t.identifier('pipeRegistry'), t.identifier('pipeRegistry'))], t.stringLiteral('@fluffjs/fluff'));
|
|
40
|
+
path.node.body.unshift(importDecl);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
30
43
|
}
|
|
31
44
|
},
|
|
32
45
|
ClassBody(path, state) {
|
|
@@ -69,6 +82,7 @@ export default function reactivePlugin() {
|
|
|
69
82
|
const privateFields = [];
|
|
70
83
|
const getterHostBindingUpdates = [];
|
|
71
84
|
const propertyHostBindingInits = [];
|
|
85
|
+
const classHostBindingDefs = [];
|
|
72
86
|
const pipeMethods = [];
|
|
73
87
|
const hostListeners = [];
|
|
74
88
|
const linkedPropertyMethods = [];
|
|
@@ -200,18 +214,26 @@ export default function reactivePlugin() {
|
|
|
200
214
|
const privateName = `__hostBinding_${propName}`;
|
|
201
215
|
const initialValue = propNode.value ?? t.identifier('undefined');
|
|
202
216
|
const privateField = t.classProperty(t.identifier(privateName), initialValue);
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
217
|
+
if (hostProperty.startsWith('class.')) {
|
|
218
|
+
const className = hostProperty.slice(6);
|
|
219
|
+
propsToRemove.push(memberPath);
|
|
220
|
+
classHostBindingDefs.push({ propName, className, privateName });
|
|
221
|
+
path.unshiftContainer('body', privateField);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
const getter = t.classMethod('get', t.identifier(propName), [], t.blockStatement([
|
|
225
|
+
t.returnStatement(t.memberExpression(t.thisExpression(), t.identifier(privateName)))
|
|
226
|
+
]));
|
|
227
|
+
const updateStatement = buildHostBindingUpdateStatement(hostProperty);
|
|
228
|
+
const setter = t.classMethod('set', t.identifier(propName), [t.identifier('__v')], t.blockStatement([
|
|
229
|
+
t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.thisExpression(), t.identifier(privateName)), t.identifier('__v'))),
|
|
230
|
+
updateStatement
|
|
231
|
+
]));
|
|
232
|
+
newMembers.push(getter, setter);
|
|
233
|
+
propsToRemove.push(memberPath);
|
|
234
|
+
propertyHostBindingInits.push({ propName, privateName });
|
|
235
|
+
path.unshiftContainer('body', privateField);
|
|
236
|
+
}
|
|
215
237
|
}
|
|
216
238
|
}
|
|
217
239
|
continue;
|
|
@@ -234,6 +256,7 @@ export default function reactivePlugin() {
|
|
|
234
256
|
continue;
|
|
235
257
|
}
|
|
236
258
|
const reactiveDecoratorIndices = [];
|
|
259
|
+
const decoratorsSnapshot = [...decorators];
|
|
237
260
|
for (const [idx, dec] of decorators.entries()) {
|
|
238
261
|
const name = getDecoratorName(dec);
|
|
239
262
|
if (name === 'Reactive' || name === 'Input') {
|
|
@@ -250,31 +273,67 @@ export default function reactivePlugin() {
|
|
|
250
273
|
const propName = propNode.key.name;
|
|
251
274
|
const privateName = `__${propName}`;
|
|
252
275
|
const initialValue = propNode.value ?? t.identifier('undefined');
|
|
276
|
+
let directionExpr = undefined;
|
|
277
|
+
let commitTriggerName = undefined;
|
|
253
278
|
state.needsPropertyImport = true;
|
|
254
279
|
state.reactiveProperties?.add(propName);
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
280
|
+
for (const dec of decoratorsSnapshot) {
|
|
281
|
+
const name = getDecoratorName(dec);
|
|
282
|
+
if (name !== 'Reactive' && name !== 'Input')
|
|
283
|
+
continue;
|
|
284
|
+
if (!t.isCallExpression(dec.expression))
|
|
285
|
+
continue;
|
|
286
|
+
const args = dec.expression.arguments;
|
|
287
|
+
if (args.length === 0)
|
|
288
|
+
continue;
|
|
289
|
+
const [optionsArg] = args;
|
|
290
|
+
if (!t.isObjectExpression(optionsArg))
|
|
291
|
+
continue;
|
|
292
|
+
for (const prop of optionsArg.properties) {
|
|
293
|
+
if (!t.isObjectProperty(prop))
|
|
294
|
+
continue;
|
|
295
|
+
if (prop.computed || !t.isIdentifier(prop.key))
|
|
296
|
+
continue;
|
|
297
|
+
if (prop.key.name === 'direction' && t.isExpression(prop.value)) {
|
|
298
|
+
directionExpr = prop.value;
|
|
299
|
+
}
|
|
300
|
+
if (prop.key.name === 'commitTrigger' && t.isStringLiteral(prop.value)) {
|
|
301
|
+
commitTriggerName = prop.value.value;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
268
305
|
const linkedMethod = linkedPropertyMethods.find(lp => lp.propertyName === propName);
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
306
|
+
const isProduction = state.opts?.production ?? false;
|
|
307
|
+
const hasDirection = directionExpr !== undefined;
|
|
308
|
+
const hasCommitTrigger = commitTriggerName !== undefined;
|
|
309
|
+
const hasLinkedMethod = linkedMethod !== undefined;
|
|
310
|
+
const useOptionsObject = !isProduction || hasDirection || hasCommitTrigger || hasLinkedMethod;
|
|
311
|
+
const propertyOptions = [];
|
|
312
|
+
if (useOptionsObject) {
|
|
313
|
+
propertyOptions.push(t.objectProperty(t.identifier('initialValue'), initialValue));
|
|
314
|
+
if (!isProduction) {
|
|
315
|
+
propertyOptions.push(t.objectProperty(t.identifier('propertyName'), t.stringLiteral(propName)));
|
|
316
|
+
}
|
|
317
|
+
if (directionExpr) {
|
|
318
|
+
propertyOptions.push(t.objectProperty(t.identifier('direction'), directionExpr));
|
|
319
|
+
}
|
|
320
|
+
if (commitTriggerName) {
|
|
321
|
+
propertyOptions.push(t.objectProperty(t.identifier('commitTrigger'), t.memberExpression(t.thisExpression(), t.identifier(`__${commitTriggerName}`))));
|
|
322
|
+
}
|
|
323
|
+
if (linkedMethod) {
|
|
324
|
+
propertyOptions.push(t.objectProperty(t.identifier('linkHandler'), t.arrowFunctionExpression([
|
|
325
|
+
t.identifier('__p')
|
|
326
|
+
], t.callExpression(t.memberExpression(t.thisExpression(), t.identifier(linkedMethod.methodName)), [
|
|
327
|
+
t.identifier('__p')
|
|
328
|
+
]))));
|
|
329
|
+
}
|
|
274
330
|
}
|
|
275
|
-
const
|
|
331
|
+
const propertyArgs = useOptionsObject
|
|
332
|
+
? [t.objectExpression(propertyOptions)]
|
|
333
|
+
: [initialValue];
|
|
334
|
+
const createPropCall = t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__createProp')), [t.stringLiteral(propName), ...propertyArgs]);
|
|
335
|
+
const privateField = t.classProperty(t.identifier(privateName), createPropCall);
|
|
276
336
|
propsToRemove.push(memberPath);
|
|
277
|
-
newMembers.push(getter, setter);
|
|
278
337
|
privateFields.push(privateField);
|
|
279
338
|
}
|
|
280
339
|
for (const p of propsToRemove) {
|
|
@@ -321,6 +380,9 @@ export default function reactivePlugin() {
|
|
|
321
380
|
}
|
|
322
381
|
const reactiveProps = state.reactiveProperties ?? new Set();
|
|
323
382
|
const constructorStatements = [];
|
|
383
|
+
for (const { propName, className, privateName } of classHostBindingDefs) {
|
|
384
|
+
constructorStatements.push(t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__defineClassHostBinding')), [t.stringLiteral(propName), t.stringLiteral(className), t.stringLiteral(privateName)])));
|
|
385
|
+
}
|
|
324
386
|
for (const updateMethodName of getterHostBindingUpdates) {
|
|
325
387
|
constructorStatements.push(t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier(updateMethodName)), [])));
|
|
326
388
|
}
|
|
@@ -394,6 +456,29 @@ export default function reactivePlugin() {
|
|
|
394
456
|
])));
|
|
395
457
|
}
|
|
396
458
|
}
|
|
459
|
+
},
|
|
460
|
+
ClassDeclaration(path, state) {
|
|
461
|
+
const decorators = path.node.decorators ?? [];
|
|
462
|
+
const pipeDecoratorIndex = findDecoratorIndex(decorators, 'Pipe');
|
|
463
|
+
if (pipeDecoratorIndex >= 0) {
|
|
464
|
+
const pipeDecorator = decorators[pipeDecoratorIndex];
|
|
465
|
+
if (t.isCallExpression(pipeDecorator.expression)) {
|
|
466
|
+
const args = pipeDecorator.expression.arguments;
|
|
467
|
+
if (args.length > 0 && t.isStringLiteral(args[0]) && path.node.id) {
|
|
468
|
+
const pipeName = args[0].value;
|
|
469
|
+
const className = path.node.id;
|
|
470
|
+
state.needsPipeRegistryImport = true;
|
|
471
|
+
decorators.splice(pipeDecoratorIndex, 1);
|
|
472
|
+
if (decorators.length === 0) {
|
|
473
|
+
path.node.decorators = null;
|
|
474
|
+
}
|
|
475
|
+
path.insertAfter([
|
|
476
|
+
t.expressionStatement(t.assignmentExpression('=', t.memberExpression(className, t.identifier('__pipeName')), t.stringLiteral(pipeName))),
|
|
477
|
+
t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('pipeRegistry'), t.identifier('set')), [t.stringLiteral(pipeName), className]))
|
|
478
|
+
]);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
397
482
|
}
|
|
398
483
|
}
|
|
399
484
|
};
|
package/fluff-esbuild-plugin.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
+
import { parse } from '@babel/parser';
|
|
5
|
+
import * as t from '@babel/types';
|
|
6
|
+
import { generate } from './BabelHelpers.js';
|
|
4
7
|
import { CodeGenerator } from './CodeGenerator.js';
|
|
5
8
|
import { ComponentCompiler } from './ComponentCompiler.js';
|
|
9
|
+
const VIRTUAL_EXPR_TABLE_ID = '@fluff/expr-table';
|
|
6
10
|
function findFluffSourcePath() {
|
|
7
11
|
const thisFile = fileURLToPath(import.meta.url);
|
|
8
12
|
const distDir = path.dirname(thisFile);
|
|
@@ -14,28 +18,139 @@ function findFluffSourcePath() {
|
|
|
14
18
|
}
|
|
15
19
|
return null;
|
|
16
20
|
}
|
|
21
|
+
function getEntryPointPath(build) {
|
|
22
|
+
const { entryPoints, stdin } = build.initialOptions;
|
|
23
|
+
if (stdin?.resolveDir) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if (Array.isArray(entryPoints) && entryPoints.length > 0) {
|
|
27
|
+
const [first] = entryPoints;
|
|
28
|
+
return typeof first === 'string' ? first : first.in;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
function detectDecorators(source) {
|
|
33
|
+
const result = { hasComponent: false, hasPipe: false };
|
|
34
|
+
try {
|
|
35
|
+
const ast = parse(source, {
|
|
36
|
+
sourceType: 'module',
|
|
37
|
+
plugins: ['typescript', 'decorators']
|
|
38
|
+
});
|
|
39
|
+
for (const node of ast.program.body) {
|
|
40
|
+
if (t.isClassDeclaration(node) && node.decorators) {
|
|
41
|
+
for (const decorator of node.decorators) {
|
|
42
|
+
if (t.isCallExpression(decorator.expression) && t.isIdentifier(decorator.expression.callee)) {
|
|
43
|
+
const { name } = decorator.expression.callee;
|
|
44
|
+
if (name === 'Component')
|
|
45
|
+
result.hasComponent = true;
|
|
46
|
+
if (name === 'Pipe')
|
|
47
|
+
result.hasPipe = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (t.isExportNamedDeclaration(node) && t.isClassDeclaration(node.declaration) && node.declaration.decorators) {
|
|
52
|
+
for (const decorator of node.declaration.decorators) {
|
|
53
|
+
if (t.isCallExpression(decorator.expression) && t.isIdentifier(decorator.expression.callee)) {
|
|
54
|
+
const { name } = decorator.expression.callee;
|
|
55
|
+
if (name === 'Component')
|
|
56
|
+
result.hasComponent = true;
|
|
57
|
+
if (name === 'Pipe')
|
|
58
|
+
result.hasPipe = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (t.isExportDefaultDeclaration(node) && t.isClassDeclaration(node.declaration) && node.declaration.decorators) {
|
|
63
|
+
for (const decorator of node.declaration.decorators) {
|
|
64
|
+
if (t.isCallExpression(decorator.expression) && t.isIdentifier(decorator.expression.callee)) {
|
|
65
|
+
const { name } = decorator.expression.callee;
|
|
66
|
+
if (name === 'Component')
|
|
67
|
+
result.hasComponent = true;
|
|
68
|
+
if (name === 'Pipe')
|
|
69
|
+
result.hasPipe = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
17
79
|
export function fluffPlugin(options) {
|
|
18
80
|
const compiler = new ComponentCompiler();
|
|
19
|
-
let componentsDiscovered = false;
|
|
20
81
|
const fluffSrcPath = findFluffSourcePath();
|
|
82
|
+
const compiledCache = new Map();
|
|
83
|
+
let entryPointPath = null;
|
|
21
84
|
// noinspection JSUnusedGlobalSymbols
|
|
22
85
|
return {
|
|
23
86
|
name: 'fluff',
|
|
24
87
|
setup(build) {
|
|
88
|
+
entryPointPath = getEntryPointPath(build);
|
|
25
89
|
build.onStart(async () => {
|
|
26
90
|
CodeGenerator.resetGlobalState();
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
91
|
+
compiledCache.clear();
|
|
92
|
+
const componentPaths = await compiler.discoverComponents(options.srcDir);
|
|
93
|
+
for (const componentPath of componentPaths) {
|
|
94
|
+
const result = await compiler.compileComponentForBundle(componentPath, options.minify, options.sourcemap, options.skipDefine, options.production);
|
|
95
|
+
compiledCache.set(componentPath, {
|
|
96
|
+
code: result.code,
|
|
97
|
+
watchFiles: result.watchFiles
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
build.onLoad({ filter: /\.ts$/ }, async (args) => {
|
|
102
|
+
const source = fs.readFileSync(args.path, 'utf-8');
|
|
103
|
+
const decorators = detectDecorators(source);
|
|
104
|
+
if (decorators.hasComponent) {
|
|
105
|
+
const cached = compiledCache.get(args.path);
|
|
106
|
+
if (cached) {
|
|
107
|
+
return {
|
|
108
|
+
contents: cached.code,
|
|
109
|
+
loader: 'js',
|
|
110
|
+
resolveDir: path.dirname(args.path),
|
|
111
|
+
watchFiles: cached.watchFiles
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const result = await compiler.compileComponentForBundle(args.path, options.minify, options.sourcemap, options.skipDefine, options.production);
|
|
115
|
+
return {
|
|
116
|
+
contents: result.code,
|
|
117
|
+
loader: 'js',
|
|
118
|
+
resolveDir: path.dirname(args.path),
|
|
119
|
+
watchFiles: result.watchFiles
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (decorators.hasPipe) {
|
|
123
|
+
const transformed = await compiler.transformPipeDecorators(source, args.path);
|
|
124
|
+
return {
|
|
125
|
+
contents: transformed,
|
|
126
|
+
loader: 'ts',
|
|
127
|
+
resolveDir: path.dirname(args.path)
|
|
128
|
+
};
|
|
30
129
|
}
|
|
130
|
+
if (!entryPointPath || args.path !== entryPointPath) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
const ast = parse(source, {
|
|
134
|
+
sourceType: 'module',
|
|
135
|
+
plugins: ['typescript', 'decorators']
|
|
136
|
+
});
|
|
137
|
+
const exprTableImport = t.importDeclaration([], t.stringLiteral(VIRTUAL_EXPR_TABLE_ID));
|
|
138
|
+
ast.program.body.push(exprTableImport);
|
|
139
|
+
const output = generate(ast, { compact: false });
|
|
140
|
+
return {
|
|
141
|
+
contents: output.code,
|
|
142
|
+
loader: 'ts',
|
|
143
|
+
resolveDir: path.dirname(args.path)
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
build.onResolve({ filter: new RegExp(`^${VIRTUAL_EXPR_TABLE_ID.replace('/', '\\/')}$`) }, () => {
|
|
147
|
+
return { path: VIRTUAL_EXPR_TABLE_ID, namespace: 'fluff-virtual' };
|
|
31
148
|
});
|
|
32
|
-
build.onLoad({ filter:
|
|
33
|
-
const
|
|
149
|
+
build.onLoad({ filter: /.*/, namespace: 'fluff-virtual' }, () => {
|
|
150
|
+
const exprTable = CodeGenerator.generateGlobalExprTable();
|
|
34
151
|
return {
|
|
35
|
-
contents:
|
|
36
|
-
loader: 'js'
|
|
37
|
-
resolveDir: path.dirname(args.path),
|
|
38
|
-
watchFiles: result.watchFiles
|
|
152
|
+
contents: exprTable || '',
|
|
153
|
+
loader: 'js'
|
|
39
154
|
};
|
|
40
155
|
});
|
|
41
156
|
if (fluffSrcPath) {
|
|
@@ -3,6 +3,7 @@ import type { BabelPluginReactiveWatchInfo } from './BabelPluginReactiveWatchInf
|
|
|
3
3
|
export interface BabelPluginReactiveState {
|
|
4
4
|
filename?: string;
|
|
5
5
|
needsPropertyImport?: boolean;
|
|
6
|
+
needsPipeRegistryImport?: boolean;
|
|
6
7
|
reactiveProperties?: Set<string>;
|
|
7
8
|
watchMethods?: BabelPluginReactiveWatchInfo[];
|
|
8
9
|
watchCalls?: BabelPluginReactiveWatchCallInfo[];
|
package/package.json
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface MarkerConfigLiteralRecord {
|
|
2
|
+
[key: string]: MarkerConfigLiteral;
|
|
3
|
+
}
|
|
4
|
+
interface MarkerConfigLiteralArray extends Array<MarkerConfigLiteral> {
|
|
5
|
+
readonly __markerConfigArrayBrand?: true;
|
|
6
|
+
}
|
|
7
|
+
type MarkerConfigLiteral = string | number | boolean | null | MarkerConfigLiteralArray | MarkerConfigLiteralRecord;
|
|
8
|
+
type MarkerConfigEntriesLiteral = [number, MarkerConfigLiteral][];
|
|
9
|
+
export declare class MarkerConfigAstReader {
|
|
10
|
+
static readMarkerConfigEntries(code: string): MarkerConfigEntriesLiteral;
|
|
11
|
+
static collectDeps(entries: MarkerConfigEntriesLiteral): string[];
|
|
12
|
+
private static collectDepsFromRecord;
|
|
13
|
+
private static collectDepsFromIf;
|
|
14
|
+
private static collectStringsFromDep;
|
|
15
|
+
private static isMarkerConfigCall;
|
|
16
|
+
private static evaluateLiteral;
|
|
17
|
+
private static readObjectKey;
|
|
18
|
+
private static isEntriesArray;
|
|
19
|
+
private static isRecord;
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=MarkerConfigAstReader.d.ts.map
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
import { traverse } from '../BabelHelpers.js';
|
|
4
|
+
export class MarkerConfigAstReader {
|
|
5
|
+
static readMarkerConfigEntries(code) {
|
|
6
|
+
const ast = parse(code, {
|
|
7
|
+
sourceType: 'module',
|
|
8
|
+
plugins: ['typescript', 'decorators']
|
|
9
|
+
});
|
|
10
|
+
let markerConfigArg = null;
|
|
11
|
+
traverse(ast, {
|
|
12
|
+
CallExpression(path) {
|
|
13
|
+
if (MarkerConfigAstReader.isMarkerConfigCall(path.node)) {
|
|
14
|
+
const [firstArg] = path.node.arguments;
|
|
15
|
+
if (firstArg && t.isExpression(firstArg)) {
|
|
16
|
+
markerConfigArg = firstArg;
|
|
17
|
+
path.stop();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
if (!markerConfigArg) {
|
|
23
|
+
throw new Error('Could not find __setMarkerConfigs call');
|
|
24
|
+
}
|
|
25
|
+
const literal = MarkerConfigAstReader.evaluateLiteral(markerConfigArg);
|
|
26
|
+
if (!MarkerConfigAstReader.isEntriesArray(literal)) {
|
|
27
|
+
throw new Error('Expected marker config entries array');
|
|
28
|
+
}
|
|
29
|
+
return literal;
|
|
30
|
+
}
|
|
31
|
+
static collectDeps(entries) {
|
|
32
|
+
const deps = [];
|
|
33
|
+
for (const [, config] of entries) {
|
|
34
|
+
if (!MarkerConfigAstReader.isRecord(config)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const configRecord = config;
|
|
38
|
+
const typeValue = configRecord.type;
|
|
39
|
+
if (typeof typeValue !== 'string') {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
switch (typeValue) {
|
|
43
|
+
case 'text':
|
|
44
|
+
MarkerConfigAstReader.collectDepsFromRecord(configRecord, deps);
|
|
45
|
+
break;
|
|
46
|
+
case 'if':
|
|
47
|
+
MarkerConfigAstReader.collectDepsFromIf(configRecord, deps);
|
|
48
|
+
break;
|
|
49
|
+
case 'for':
|
|
50
|
+
MarkerConfigAstReader.collectDepsFromRecord(configRecord, deps);
|
|
51
|
+
break;
|
|
52
|
+
case 'switch':
|
|
53
|
+
MarkerConfigAstReader.collectDepsFromRecord(configRecord, deps);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return deps;
|
|
58
|
+
}
|
|
59
|
+
static collectDepsFromRecord(configRecord, deps) {
|
|
60
|
+
const configDeps = configRecord.deps;
|
|
61
|
+
if (Array.isArray(configDeps)) {
|
|
62
|
+
for (const dep of configDeps) {
|
|
63
|
+
MarkerConfigAstReader.collectStringsFromDep(dep, deps);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
static collectDepsFromIf(configRecord, deps) {
|
|
68
|
+
const { branches } = configRecord;
|
|
69
|
+
if (!Array.isArray(branches)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const branch of branches) {
|
|
73
|
+
if (!MarkerConfigAstReader.isRecord(branch)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
MarkerConfigAstReader.collectDepsFromRecord(branch, deps);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
static collectStringsFromDep(dep, deps) {
|
|
80
|
+
if (typeof dep === 'string') {
|
|
81
|
+
deps.push(dep);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(dep)) {
|
|
85
|
+
for (const item of dep) {
|
|
86
|
+
MarkerConfigAstReader.collectStringsFromDep(item, deps);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
static isMarkerConfigCall(node) {
|
|
91
|
+
if (!t.isMemberExpression(node.callee)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (!t.isIdentifier(node.callee.property)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return node.callee.property.name === '__setMarkerConfigs';
|
|
98
|
+
}
|
|
99
|
+
static evaluateLiteral(node) {
|
|
100
|
+
if (t.isStringLiteral(node)) {
|
|
101
|
+
return node.value;
|
|
102
|
+
}
|
|
103
|
+
if (t.isNumericLiteral(node)) {
|
|
104
|
+
return node.value;
|
|
105
|
+
}
|
|
106
|
+
if (t.isBooleanLiteral(node)) {
|
|
107
|
+
return node.value;
|
|
108
|
+
}
|
|
109
|
+
if (t.isNullLiteral(node)) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
if (t.isArrayExpression(node)) {
|
|
113
|
+
const items = [];
|
|
114
|
+
for (const element of node.elements) {
|
|
115
|
+
if (!element || !t.isExpression(element)) {
|
|
116
|
+
throw new Error('Unexpected array element in marker config literal');
|
|
117
|
+
}
|
|
118
|
+
items.push(MarkerConfigAstReader.evaluateLiteral(element));
|
|
119
|
+
}
|
|
120
|
+
return items;
|
|
121
|
+
}
|
|
122
|
+
if (t.isObjectExpression(node)) {
|
|
123
|
+
const result = {};
|
|
124
|
+
for (const prop of node.properties) {
|
|
125
|
+
if (!t.isObjectProperty(prop)) {
|
|
126
|
+
throw new Error('Unexpected object property in marker config literal');
|
|
127
|
+
}
|
|
128
|
+
const key = MarkerConfigAstReader.readObjectKey(prop.key);
|
|
129
|
+
if (!t.isExpression(prop.value)) {
|
|
130
|
+
throw new Error('Unexpected object property value in marker config literal');
|
|
131
|
+
}
|
|
132
|
+
result[key] = MarkerConfigAstReader.evaluateLiteral(prop.value);
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
throw new Error('Unsupported marker config literal');
|
|
137
|
+
}
|
|
138
|
+
static readObjectKey(key) {
|
|
139
|
+
if (t.isIdentifier(key)) {
|
|
140
|
+
return key.name;
|
|
141
|
+
}
|
|
142
|
+
if (t.isStringLiteral(key)) {
|
|
143
|
+
return key.value;
|
|
144
|
+
}
|
|
145
|
+
throw new Error('Unsupported object key in marker config literal');
|
|
146
|
+
}
|
|
147
|
+
static isEntriesArray(value) {
|
|
148
|
+
if (!Array.isArray(value)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
return value.every(entry => {
|
|
152
|
+
if (!Array.isArray(entry) || entry.length !== 2) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const [id, config] = entry;
|
|
156
|
+
return typeof id === 'number' && MarkerConfigAstReader.isRecord(config);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
static isRecord(value) {
|
|
160
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
161
|
+
}
|
|
162
|
+
}
|