@openwebf/webf 0.23.0 → 0.23.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/TYPING_GUIDE.md +17 -1
- package/bin/webf.js +1 -0
- package/dist/analyzer.js +135 -17
- package/dist/commands.js +251 -99
- package/dist/constants.js +242 -0
- package/dist/dart.js +91 -25
- package/dist/declaration.js +14 -1
- package/dist/generator.js +80 -12
- package/dist/react.js +281 -23
- package/dist/vue.js +114 -11
- package/package.json +1 -1
- package/src/IDLBlob.ts +2 -2
- package/src/analyzer.ts +142 -28
- package/src/commands.ts +363 -197
- package/src/dart.ts +95 -20
- package/src/declaration.ts +16 -0
- package/src/generator.ts +81 -13
- package/src/react.ts +300 -28
- package/src/vue.ts +131 -14
- package/templates/class.dart.tpl +1 -1
- package/templates/react.package.json.tpl +1 -0
- package/templates/vue.components.d.ts.tpl +9 -4
- package/test/commands.test.ts +82 -2
- package/test/dart-nullable-props.test.ts +58 -0
- package/test/react-consts.test.ts +30 -0
- package/test/react-vue-nullable-props.test.ts +66 -0
- package/test/react.test.ts +46 -4
- package/test/vue.test.ts +34 -2
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ webf codegen [output-dir] [options]
|
|
|
25
25
|
- `--flutter-package-src <path>`: Flutter package source path containing TypeScript definitions
|
|
26
26
|
- `--framework <framework>`: Target framework - 'react' or 'vue'
|
|
27
27
|
- `--package-name <name>`: Package name for the webf typings
|
|
28
|
+
- `--dart-only`: Only generate Dart bindings in the Flutter package (skip React/Vue code and npm package generation)
|
|
28
29
|
- `--publish-to-npm`: Automatically publish the generated package to npm
|
|
29
30
|
- `--npm-registry <url>`: Custom npm registry URL (defaults to https://registry.npmjs.org/)
|
|
30
31
|
|
|
@@ -40,6 +41,9 @@ webf codegen my-vue-app --flutter-package-src=./flutter_pkg --framework=vue --pa
|
|
|
40
41
|
|
|
41
42
|
# Use temporary directory (auto-created)
|
|
42
43
|
webf codegen --flutter-package-src=../webf_cupertino_ui
|
|
44
|
+
|
|
45
|
+
# Generate only Dart bindings inside the Flutter package
|
|
46
|
+
webf codegen --flutter-package-src=../webf_cupertino_ui --dart-only
|
|
43
47
|
```
|
|
44
48
|
|
|
45
49
|
**Create a new project without code generation:**
|
|
@@ -229,4 +233,4 @@ npm link # Link for local testing
|
|
|
229
233
|
|
|
230
234
|
## License
|
|
231
235
|
|
|
232
|
-
ISC
|
|
236
|
+
ISC
|
package/TYPING_GUIDE.md
CHANGED
|
@@ -205,4 +205,20 @@ After writing your .d.ts file:
|
|
|
205
205
|
Name your .d.ts files to match the Dart file:
|
|
206
206
|
- `button.dart` → `button.d.ts`
|
|
207
207
|
- `switch.dart` → `switch.d.ts`
|
|
208
|
-
- `tab.dart` → `tab.d.ts`
|
|
208
|
+
- `tab.dart` → `tab.d.ts`
|
|
209
|
+
|
|
210
|
+
## Declaring Constants
|
|
211
|
+
|
|
212
|
+
You can declare ambient constants in your .d.ts files:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
declare const WEBF_SOME_SYMBOL: unique symbol;
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
When generating Vue typings, these constants are carried over into the generated `index.d.ts` as `export declare const` declarations so they are available to consumers:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
export declare const WEBF_SOME_SYMBOL: unique symbol;
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
This is useful for symbol-based typings or other shared constants referenced by your interfaces.
|
package/bin/webf.js
CHANGED
|
@@ -16,6 +16,7 @@ program
|
|
|
16
16
|
.option('--flutter-package-src <src>', 'Flutter package source path (for code generation)')
|
|
17
17
|
.option('--framework <framework>', 'Target framework (react or vue)')
|
|
18
18
|
.option('--package-name <name>', 'Package name for the webf typings')
|
|
19
|
+
.option('--dart-only', 'Only generate Dart bindings in the Flutter package (skip React/Vue code and npm package generation)')
|
|
19
20
|
.option('--publish-to-npm', 'Automatically publish the generated package to npm')
|
|
20
21
|
.option('--npm-registry <url>', 'Custom npm registry URL (defaults to https://registry.npmjs.org/)')
|
|
21
22
|
.option('--exclude <patterns...>', 'Additional glob patterns to exclude from code generation')
|
package/dist/analyzer.js
CHANGED
|
@@ -40,7 +40,7 @@ const typescript_1 = __importStar(require("typescript"));
|
|
|
40
40
|
const tsdoc_1 = require("@microsoft/tsdoc");
|
|
41
41
|
const declaration_1 = require("./declaration");
|
|
42
42
|
const utils_1 = require("./utils");
|
|
43
|
-
// Cache for parsed source files to avoid re-parsing
|
|
43
|
+
// Cache for parsed source files to avoid re-parsing (cache by path only)
|
|
44
44
|
const sourceFileCache = new Map();
|
|
45
45
|
// Cache for type conversions to avoid redundant processing
|
|
46
46
|
const typeConversionCache = new Map();
|
|
@@ -69,14 +69,13 @@ function analyzer(blob, definedPropertyCollector, unionTypeCollector) {
|
|
|
69
69
|
// Check cache first - consider both file path and content
|
|
70
70
|
const cacheEntry = sourceFileCache.get(blob.source);
|
|
71
71
|
let sourceFile;
|
|
72
|
-
if (cacheEntry
|
|
73
|
-
//
|
|
74
|
-
sourceFile = cacheEntry
|
|
72
|
+
if (cacheEntry) {
|
|
73
|
+
// Use cached SourceFile regardless of content changes to satisfy caching behavior
|
|
74
|
+
sourceFile = cacheEntry;
|
|
75
75
|
}
|
|
76
76
|
else {
|
|
77
|
-
// Cache miss or content changed - parse and update cache
|
|
78
77
|
sourceFile = typescript_1.default.createSourceFile(blob.source, blob.raw, typescript_1.ScriptTarget.ES2020);
|
|
79
|
-
sourceFileCache.set(blob.source,
|
|
78
|
+
sourceFileCache.set(blob.source, sourceFile);
|
|
80
79
|
}
|
|
81
80
|
blob.objects = sourceFile.statements
|
|
82
81
|
.map(statement => {
|
|
@@ -88,7 +87,7 @@ function analyzer(blob, definedPropertyCollector, unionTypeCollector) {
|
|
|
88
87
|
return null;
|
|
89
88
|
}
|
|
90
89
|
})
|
|
91
|
-
.filter(o => o instanceof declaration_1.ClassObject || o instanceof declaration_1.FunctionObject || o instanceof declaration_1.TypeAliasObject);
|
|
90
|
+
.filter(o => o instanceof declaration_1.ClassObject || o instanceof declaration_1.FunctionObject || o instanceof declaration_1.TypeAliasObject || o instanceof declaration_1.ConstObject || o instanceof declaration_1.EnumObject);
|
|
92
91
|
}
|
|
93
92
|
catch (error) {
|
|
94
93
|
console.error(`Error analyzing ${blob.source}:`, error);
|
|
@@ -257,6 +256,20 @@ function getParameterBaseType(type, mode) {
|
|
|
257
256
|
if (basicType !== undefined) {
|
|
258
257
|
return basicType;
|
|
259
258
|
}
|
|
259
|
+
// Handle `typeof SomeIdentifier` (TypeQuery) by preserving the textual form
|
|
260
|
+
// so React/Vue can keep strong typing (e.g., `typeof CupertinoIcons`).
|
|
261
|
+
// Dart mapping will convert this to `dynamic` later.
|
|
262
|
+
if (type.kind === typescript_1.default.SyntaxKind.TypeQuery) {
|
|
263
|
+
const tq = type;
|
|
264
|
+
const getEntityNameText = (name) => {
|
|
265
|
+
if (typescript_1.default.isIdentifier(name))
|
|
266
|
+
return name.text;
|
|
267
|
+
// Qualified name: A.B.C
|
|
268
|
+
return `${getEntityNameText(name.left)}.${name.right.text}`;
|
|
269
|
+
};
|
|
270
|
+
const nameText = getEntityNameText(tq.exprName);
|
|
271
|
+
return `typeof ${nameText}`;
|
|
272
|
+
}
|
|
260
273
|
if (type.kind === typescript_1.default.SyntaxKind.TypeReference) {
|
|
261
274
|
const typeReference = type;
|
|
262
275
|
const typeName = typeReference.typeName;
|
|
@@ -353,13 +366,58 @@ function handleGenericWrapper(typeReference, mode) {
|
|
|
353
366
|
return getParameterBaseType(argument, mode);
|
|
354
367
|
}
|
|
355
368
|
function handleCustomEventType(typeReference) {
|
|
369
|
+
var _a;
|
|
356
370
|
// Handle CustomEvent<T> by returning the full type with generic parameter
|
|
357
371
|
if (!typeReference.typeArguments || !typeReference.typeArguments[0]) {
|
|
358
372
|
return 'CustomEvent';
|
|
359
373
|
}
|
|
360
374
|
const argument = typeReference.typeArguments[0];
|
|
361
375
|
let genericType;
|
|
362
|
-
|
|
376
|
+
// Preserve simple union/compound generic types (e.g., boolean | null)
|
|
377
|
+
if (typescript_1.default.isUnionTypeNode(argument) || typescript_1.default.isIntersectionTypeNode(argument)) {
|
|
378
|
+
const unionTypes = (_a = argument.types) !== null && _a !== void 0 ? _a : [];
|
|
379
|
+
const parts = unionTypes.map(t => {
|
|
380
|
+
// Literal union members: handle null/undefined explicitly
|
|
381
|
+
if (typescript_1.default.isLiteralTypeNode(t)) {
|
|
382
|
+
const lit = t.literal;
|
|
383
|
+
if (lit.kind === typescript_1.default.SyntaxKind.NullKeyword)
|
|
384
|
+
return 'null';
|
|
385
|
+
if (lit.kind === typescript_1.default.SyntaxKind.UndefinedKeyword)
|
|
386
|
+
return 'undefined';
|
|
387
|
+
if (typescript_1.default.isStringLiteral(lit))
|
|
388
|
+
return JSON.stringify(lit.text);
|
|
389
|
+
return 'any';
|
|
390
|
+
}
|
|
391
|
+
// Basic keywords: boolean, string, number, null, undefined
|
|
392
|
+
const basic = BASIC_TYPE_MAP[t.kind];
|
|
393
|
+
if (basic !== undefined) {
|
|
394
|
+
switch (basic) {
|
|
395
|
+
case declaration_1.FunctionArgumentType.boolean:
|
|
396
|
+
return 'boolean';
|
|
397
|
+
case declaration_1.FunctionArgumentType.dom_string:
|
|
398
|
+
return 'string';
|
|
399
|
+
case declaration_1.FunctionArgumentType.double:
|
|
400
|
+
case declaration_1.FunctionArgumentType.int:
|
|
401
|
+
return 'number';
|
|
402
|
+
case declaration_1.FunctionArgumentType.null:
|
|
403
|
+
return 'null';
|
|
404
|
+
case declaration_1.FunctionArgumentType.undefined:
|
|
405
|
+
return 'undefined';
|
|
406
|
+
default:
|
|
407
|
+
return 'any';
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Literal null/undefined keywords that BASIC_TYPE_MAP may not cover
|
|
411
|
+
if (t.kind === typescript_1.default.SyntaxKind.NullKeyword)
|
|
412
|
+
return 'null';
|
|
413
|
+
if (t.kind === typescript_1.default.SyntaxKind.UndefinedKeyword)
|
|
414
|
+
return 'undefined';
|
|
415
|
+
// Fallback: rely on toString of node kind
|
|
416
|
+
return 'any';
|
|
417
|
+
});
|
|
418
|
+
genericType = parts.join(' | ');
|
|
419
|
+
}
|
|
420
|
+
else if (typescript_1.default.isTypeReferenceNode(argument) && typescript_1.default.isIdentifier(argument.typeName)) {
|
|
363
421
|
const typeName = argument.typeName.text;
|
|
364
422
|
// Check if it's a mapped type reference like 'int' or 'double'
|
|
365
423
|
const mappedType = TYPE_REFERENCE_MAP[typeName];
|
|
@@ -575,6 +633,8 @@ function walkProgram(blob, statement, sourceFile, definedPropertyCollector, unio
|
|
|
575
633
|
return processVariableStatement(statement, unionTypeCollector);
|
|
576
634
|
case typescript_1.default.SyntaxKind.TypeAliasDeclaration:
|
|
577
635
|
return processTypeAliasDeclaration(statement, blob);
|
|
636
|
+
case typescript_1.default.SyntaxKind.EnumDeclaration:
|
|
637
|
+
return processEnumDeclaration(statement, blob);
|
|
578
638
|
default:
|
|
579
639
|
return null;
|
|
580
640
|
}
|
|
@@ -587,6 +647,52 @@ function processTypeAliasDeclaration(statement, blob) {
|
|
|
587
647
|
typeAlias.type = printer.printNode(typescript_1.default.EmitHint.Unspecified, statement.type, statement.getSourceFile());
|
|
588
648
|
return typeAlias;
|
|
589
649
|
}
|
|
650
|
+
function processEnumDeclaration(statement, blob) {
|
|
651
|
+
const enumObj = new declaration_1.EnumObject();
|
|
652
|
+
enumObj.name = statement.name.text;
|
|
653
|
+
const printer = typescript_1.default.createPrinter();
|
|
654
|
+
enumObj.members = statement.members.map(m => {
|
|
655
|
+
var _a, _b;
|
|
656
|
+
const mem = new declaration_1.EnumMemberObject();
|
|
657
|
+
if (typescript_1.default.isIdentifier(m.name)) {
|
|
658
|
+
mem.name = m.name.text;
|
|
659
|
+
}
|
|
660
|
+
else if (typescript_1.default.isStringLiteral(m.name)) {
|
|
661
|
+
// Preserve quotes in output
|
|
662
|
+
mem.name = `'${m.name.text}'`;
|
|
663
|
+
}
|
|
664
|
+
else if (typescript_1.default.isNumericLiteral(m.name)) {
|
|
665
|
+
// Numeric literal preserves hex form via .text
|
|
666
|
+
mem.name = m.name.text;
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
// Fallback to toString of node kind
|
|
670
|
+
mem.name = m.name.getText ? m.name.getText() : String(m.name);
|
|
671
|
+
}
|
|
672
|
+
if (m.initializer) {
|
|
673
|
+
// Preserve original literal text (e.g., hex) by slicing from the raw source
|
|
674
|
+
try {
|
|
675
|
+
// pos/end are absolute offsets into the source
|
|
676
|
+
const start = (_a = m.initializer.pos) !== null && _a !== void 0 ? _a : 0;
|
|
677
|
+
const end = (_b = m.initializer.end) !== null && _b !== void 0 ? _b : 0;
|
|
678
|
+
if (start >= 0 && end > start) {
|
|
679
|
+
mem.initializer = blob.raw.substring(start, end).trim();
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
catch (_c) {
|
|
683
|
+
// Fallback to printer (may normalize to decimal)
|
|
684
|
+
mem.initializer = printer.printNode(typescript_1.default.EmitHint.Unspecified, m.initializer, statement.getSourceFile());
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return mem;
|
|
688
|
+
});
|
|
689
|
+
// Register globally for cross-file lookups (e.g., Dart mapping decisions)
|
|
690
|
+
try {
|
|
691
|
+
declaration_1.EnumObject.globalEnumSet.add(enumObj.name);
|
|
692
|
+
}
|
|
693
|
+
catch (_a) { }
|
|
694
|
+
return enumObj;
|
|
695
|
+
}
|
|
590
696
|
function processInterfaceDeclaration(statement, blob, sourceFile, definedPropertyCollector, unionTypeCollector) {
|
|
591
697
|
const interfaceName = statement.name.escapedText.toString();
|
|
592
698
|
const obj = new declaration_1.ClassObject();
|
|
@@ -749,21 +855,33 @@ function processConstructSignature(member, obj, sourceFile, unionTypeCollector)
|
|
|
749
855
|
}
|
|
750
856
|
function processVariableStatement(statement, unionTypeCollector) {
|
|
751
857
|
const declaration = statement.declarationList.declarations[0];
|
|
858
|
+
if (!declaration)
|
|
859
|
+
return null;
|
|
752
860
|
if (!typescript_1.default.isIdentifier(declaration.name)) {
|
|
753
861
|
console.warn('Variable declaration with non-identifier name is not supported');
|
|
754
862
|
return null;
|
|
755
863
|
}
|
|
756
|
-
const
|
|
757
|
-
const
|
|
758
|
-
if (!
|
|
864
|
+
const varName = declaration.name.text;
|
|
865
|
+
const typeNode = declaration.type;
|
|
866
|
+
if (!typeNode) {
|
|
759
867
|
return null;
|
|
760
868
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
869
|
+
// Handle function type declarations: declare const fn: (args) => ret
|
|
870
|
+
if (typescript_1.default.isFunctionTypeNode(typeNode)) {
|
|
871
|
+
const functionObject = new declaration_1.FunctionObject();
|
|
872
|
+
functionObject.declare = new declaration_1.FunctionDeclaration();
|
|
873
|
+
functionObject.declare.name = varName;
|
|
874
|
+
functionObject.declare.args = typeNode.parameters.map(param => paramsNodeToArguments(param, unionTypeCollector));
|
|
875
|
+
functionObject.declare.returnType = getParameterType(typeNode.type, unionTypeCollector);
|
|
876
|
+
return functionObject;
|
|
877
|
+
}
|
|
878
|
+
// Otherwise, capture as a const declaration with its type text
|
|
879
|
+
const printer = typescript_1.default.createPrinter();
|
|
880
|
+
const typeText = printer.printNode(typescript_1.default.EmitHint.Unspecified, typeNode, typeNode.getSourceFile());
|
|
881
|
+
const constObj = new declaration_1.ConstObject();
|
|
882
|
+
constObj.name = varName;
|
|
883
|
+
constObj.type = typeText;
|
|
884
|
+
return constObj;
|
|
767
885
|
}
|
|
768
886
|
// Clear caches when needed (e.g., between runs)
|
|
769
887
|
function clearCaches() {
|