@openwebf/webf 0.22.4 → 0.22.6
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/TYPING_GUIDE.md +208 -0
- package/dist/commands.js +25 -6
- package/global.d.ts +2 -0
- package/package.json +5 -2
- package/src/commands.ts +29 -5
- package/tsconfig.json +30 -0
package/TYPING_GUIDE.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# WebF TypeScript Definition Files (.d.ts) Writing Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to write TypeScript definition files (.d.ts) for WebF custom elements that will be parsed by the WebF CLI to generate Dart bindings and React/Vue components.
|
|
4
|
+
|
|
5
|
+
## Basic Structure
|
|
6
|
+
|
|
7
|
+
Each WebF custom element should have two interfaces:
|
|
8
|
+
1. `<ComponentName>Properties` - Defines the element's properties/attributes
|
|
9
|
+
2. `<ComponentName>Events` - Defines the element's events
|
|
10
|
+
|
|
11
|
+
The component name is derived by removing the "Properties" or "Events" suffix.
|
|
12
|
+
|
|
13
|
+
## Interface Naming Convention
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// For a component named "FlutterCupertinoButton"
|
|
17
|
+
interface FlutterCupertinoButtonProperties {
|
|
18
|
+
// properties...
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface FlutterCupertinoButtonEvents {
|
|
22
|
+
// events...
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Properties Interface
|
|
27
|
+
|
|
28
|
+
### Basic Property Types
|
|
29
|
+
|
|
30
|
+
The CLI supports the following TypeScript types that map to Dart types:
|
|
31
|
+
|
|
32
|
+
- `string` → `String` (Dart)
|
|
33
|
+
- `number` → `double` (Dart)
|
|
34
|
+
- `int` → `int` (Dart) - Use type alias `type int = number`
|
|
35
|
+
- `boolean` → `bool` (Dart)
|
|
36
|
+
- `any` → `dynamic` (Dart)
|
|
37
|
+
- `void` → `void` (Dart)
|
|
38
|
+
|
|
39
|
+
### Property Syntax
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
interface FlutterCupertinoButtonProperties {
|
|
43
|
+
// Required property
|
|
44
|
+
variant: string;
|
|
45
|
+
|
|
46
|
+
// Optional property (use ? modifier)
|
|
47
|
+
size?: string;
|
|
48
|
+
|
|
49
|
+
// Boolean property (always non-nullable in Dart)
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
|
|
52
|
+
// Kebab-case properties (for HTML attributes)
|
|
53
|
+
'pressed-opacity'?: string;
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Important Rules for Properties
|
|
58
|
+
|
|
59
|
+
1. **Boolean properties are always non-nullable** in the generated Dart code, even if marked as optional with `?`
|
|
60
|
+
2. **Kebab-case properties** should be quoted (e.g., `'pressed-opacity'`)
|
|
61
|
+
3. **Optional properties** use the `?` modifier and will be nullable in Dart (except booleans)
|
|
62
|
+
4. **Methods in Properties interface** become instance methods on the element
|
|
63
|
+
|
|
64
|
+
### Methods in Properties
|
|
65
|
+
|
|
66
|
+
Methods defined in the Properties interface become callable methods on the element:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
interface FlutterCupertinoInputProperties {
|
|
70
|
+
// Properties
|
|
71
|
+
val?: string;
|
|
72
|
+
placeholder?: string;
|
|
73
|
+
|
|
74
|
+
// Methods
|
|
75
|
+
getValue(): string;
|
|
76
|
+
setValue(value: string): void;
|
|
77
|
+
focus(): void;
|
|
78
|
+
blur(): void;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Events Interface
|
|
83
|
+
|
|
84
|
+
Events are defined as properties of the Events interface, where:
|
|
85
|
+
- Property name = event name
|
|
86
|
+
- Property type = event type (usually `Event` or `CustomEvent<T>`)
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
interface FlutterCupertinoButtonEvents {
|
|
90
|
+
// Standard DOM event
|
|
91
|
+
click: Event;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface FlutterCupertinoInputEvents {
|
|
95
|
+
// CustomEvent with string detail
|
|
96
|
+
input: CustomEvent<string>;
|
|
97
|
+
submit: CustomEvent<string>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface FlutterCupertinoSwitchEvents {
|
|
101
|
+
// CustomEvent with boolean detail
|
|
102
|
+
change: CustomEvent<boolean>;
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Event Type Guidelines
|
|
107
|
+
|
|
108
|
+
- Use `Event` for standard DOM events
|
|
109
|
+
- Use `CustomEvent<T>` for custom events with data:
|
|
110
|
+
- `CustomEvent<string>` for string data
|
|
111
|
+
- `CustomEvent<number>` for numeric data
|
|
112
|
+
- `CustomEvent<boolean>` for boolean data
|
|
113
|
+
|
|
114
|
+
## Attribute Handling
|
|
115
|
+
|
|
116
|
+
The CLI automatically handles type conversion for HTML attributes:
|
|
117
|
+
|
|
118
|
+
1. **Boolean attributes**:
|
|
119
|
+
- HTML: `disabled` or `disabled="true"` → Dart: `true`
|
|
120
|
+
- HTML: `disabled="false"` or absent → Dart: `false`
|
|
121
|
+
|
|
122
|
+
2. **Numeric attributes**:
|
|
123
|
+
- HTML: `maxlength="100"` → Dart: `100` (int)
|
|
124
|
+
- HTML: `opacity="0.5"` → Dart: `0.5` (double)
|
|
125
|
+
|
|
126
|
+
3. **String attributes**: Passed through as-is
|
|
127
|
+
|
|
128
|
+
## Complete Example
|
|
129
|
+
|
|
130
|
+
Here's a complete example for a switch component:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// Type alias for int (optional, for clarity)
|
|
134
|
+
type int = number;
|
|
135
|
+
|
|
136
|
+
interface FlutterCupertinoSwitchProperties {
|
|
137
|
+
// Boolean property (non-nullable in Dart)
|
|
138
|
+
checked?: boolean;
|
|
139
|
+
|
|
140
|
+
// Boolean property
|
|
141
|
+
disabled?: boolean;
|
|
142
|
+
|
|
143
|
+
// Kebab-case string properties
|
|
144
|
+
'active-color'?: string;
|
|
145
|
+
'inactive-color'?: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface FlutterCupertinoSwitchEvents {
|
|
149
|
+
// CustomEvent with boolean detail
|
|
150
|
+
change: CustomEvent<boolean>;
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Special Types and Features
|
|
155
|
+
|
|
156
|
+
### Arrays
|
|
157
|
+
Arrays are supported but rarely used in element properties:
|
|
158
|
+
```typescript
|
|
159
|
+
interface ExampleProperties {
|
|
160
|
+
items?: string[]; // Generates String[]? in Dart
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Object Types
|
|
165
|
+
Reference other interfaces for complex types:
|
|
166
|
+
```typescript
|
|
167
|
+
interface ItemData {
|
|
168
|
+
id: string;
|
|
169
|
+
label: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
interface ListProperties {
|
|
173
|
+
selectedItem?: ItemData;
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Best Practices
|
|
178
|
+
|
|
179
|
+
1. **Keep interfaces simple** - Properties should represent HTML attributes or simple methods
|
|
180
|
+
2. **Use optional properties** for all attributes that have default values
|
|
181
|
+
3. **Document complex properties** with JSDoc comments (these are preserved in generated code)
|
|
182
|
+
4. **Follow naming conventions**:
|
|
183
|
+
- PascalCase for interface names
|
|
184
|
+
- camelCase for property names (except kebab-case HTML attributes)
|
|
185
|
+
- Properties interface name must end with "Properties"
|
|
186
|
+
- Events interface name must end with "Events"
|
|
187
|
+
|
|
188
|
+
## What to Avoid
|
|
189
|
+
|
|
190
|
+
1. **Don't use complex union types** - Keep types simple
|
|
191
|
+
2. **Don't use generics** in property types (except CustomEvent<T>)
|
|
192
|
+
3. **Don't use function types** as properties - Define them as methods instead
|
|
193
|
+
4. **Don't forget the Events interface** - Even if there are no events, include an empty interface
|
|
194
|
+
|
|
195
|
+
## Testing Your Definitions
|
|
196
|
+
|
|
197
|
+
After writing your .d.ts file:
|
|
198
|
+
|
|
199
|
+
1. Run the CLI generator to ensure it parses correctly
|
|
200
|
+
2. Check the generated Dart bindings match your expectations
|
|
201
|
+
3. Verify the React/Vue components have the correct prop types
|
|
202
|
+
|
|
203
|
+
## File Naming
|
|
204
|
+
|
|
205
|
+
Name your .d.ts files to match the Dart file:
|
|
206
|
+
- `button.dart` → `button.d.ts`
|
|
207
|
+
- `switch.dart` → `switch.d.ts`
|
|
208
|
+
- `tab.dart` → `tab.d.ts`
|
package/dist/commands.js
CHANGED
|
@@ -317,6 +317,8 @@ function generateCommand(distPath, options) {
|
|
|
317
317
|
const hasTsConfig = fs_1.default.existsSync(tsConfigPath);
|
|
318
318
|
// Determine if we need to create a new project
|
|
319
319
|
const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
|
|
320
|
+
// Track if this is an existing project (has all required files)
|
|
321
|
+
const isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
|
|
320
322
|
let framework = options.framework;
|
|
321
323
|
let packageName = options.packageName;
|
|
322
324
|
// Validate and sanitize package name if provided
|
|
@@ -526,7 +528,7 @@ function generateCommand(distPath, options) {
|
|
|
526
528
|
// Handle npm publishing if requested via command line option
|
|
527
529
|
if (options.publishToNpm && framework) {
|
|
528
530
|
try {
|
|
529
|
-
yield buildAndPublishPackage(resolvedDistPath, options.npmRegistry);
|
|
531
|
+
yield buildAndPublishPackage(resolvedDistPath, options.npmRegistry, isExistingProject);
|
|
530
532
|
}
|
|
531
533
|
catch (error) {
|
|
532
534
|
console.error('\nError during npm publish:', error);
|
|
@@ -561,7 +563,7 @@ function generateCommand(distPath, options) {
|
|
|
561
563
|
}
|
|
562
564
|
}]);
|
|
563
565
|
try {
|
|
564
|
-
yield buildAndPublishPackage(resolvedDistPath, registryAnswer.registry || undefined);
|
|
566
|
+
yield buildAndPublishPackage(resolvedDistPath, registryAnswer.registry || undefined, isExistingProject);
|
|
565
567
|
}
|
|
566
568
|
catch (error) {
|
|
567
569
|
console.error('\nError during npm publish:', error);
|
|
@@ -652,17 +654,34 @@ function buildPackage(packagePath) {
|
|
|
652
654
|
}
|
|
653
655
|
});
|
|
654
656
|
}
|
|
655
|
-
function buildAndPublishPackage(
|
|
656
|
-
return __awaiter(this,
|
|
657
|
+
function buildAndPublishPackage(packagePath_1, registry_1) {
|
|
658
|
+
return __awaiter(this, arguments, void 0, function* (packagePath, registry, isExistingProject = false) {
|
|
657
659
|
const packageJsonPath = path_1.default.join(packagePath, 'package.json');
|
|
658
660
|
if (!fs_1.default.existsSync(packageJsonPath)) {
|
|
659
661
|
throw new Error(`No package.json found in ${packagePath}`);
|
|
660
662
|
}
|
|
661
|
-
|
|
663
|
+
let packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
|
|
662
664
|
const packageName = packageJson.name;
|
|
663
|
-
|
|
665
|
+
let packageVersion = packageJson.version;
|
|
664
666
|
// First, ensure dependencies are installed and build the package
|
|
665
667
|
yield buildPackage(packagePath);
|
|
668
|
+
// If this is an existing project, increment the patch version before publishing
|
|
669
|
+
if (isExistingProject) {
|
|
670
|
+
console.log(`\nIncrementing version for existing project...`);
|
|
671
|
+
const versionResult = (0, child_process_1.spawnSync)(NPM, ['version', 'patch', '--no-git-tag-version'], {
|
|
672
|
+
cwd: packagePath,
|
|
673
|
+
encoding: 'utf-8',
|
|
674
|
+
stdio: 'pipe'
|
|
675
|
+
});
|
|
676
|
+
if (versionResult.status !== 0) {
|
|
677
|
+
console.error('Failed to increment version:', versionResult.stderr);
|
|
678
|
+
throw new Error('Failed to increment version');
|
|
679
|
+
}
|
|
680
|
+
// Re-read package.json to get the new version
|
|
681
|
+
packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
|
|
682
|
+
packageVersion = packageJson.version;
|
|
683
|
+
console.log(`Version updated to ${packageVersion}`);
|
|
684
|
+
}
|
|
666
685
|
// Set registry if provided
|
|
667
686
|
if (registry) {
|
|
668
687
|
console.log(`\nUsing npm registry: ${registry}`);
|
package/global.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openwebf/webf",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.6",
|
|
4
4
|
"description": "Command line tools for WebF",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
"dist",
|
|
12
12
|
"src",
|
|
13
13
|
"templates",
|
|
14
|
-
"test"
|
|
14
|
+
"test",
|
|
15
|
+
"global.d.ts",
|
|
16
|
+
"tsconfig.json",
|
|
17
|
+
"TYPING_GUIDE.md"
|
|
15
18
|
],
|
|
16
19
|
"scripts": {
|
|
17
20
|
"build": "tsc",
|
package/src/commands.ts
CHANGED
|
@@ -395,6 +395,9 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
395
395
|
// Determine if we need to create a new project
|
|
396
396
|
const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
|
|
397
397
|
|
|
398
|
+
// Track if this is an existing project (has all required files)
|
|
399
|
+
const isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
|
|
400
|
+
|
|
398
401
|
let framework = options.framework;
|
|
399
402
|
let packageName = options.packageName;
|
|
400
403
|
|
|
@@ -620,7 +623,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
620
623
|
// Handle npm publishing if requested via command line option
|
|
621
624
|
if (options.publishToNpm && framework) {
|
|
622
625
|
try {
|
|
623
|
-
await buildAndPublishPackage(resolvedDistPath, options.npmRegistry);
|
|
626
|
+
await buildAndPublishPackage(resolvedDistPath, options.npmRegistry, isExistingProject);
|
|
624
627
|
} catch (error) {
|
|
625
628
|
console.error('\nError during npm publish:', error);
|
|
626
629
|
process.exit(1);
|
|
@@ -655,7 +658,8 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
655
658
|
try {
|
|
656
659
|
await buildAndPublishPackage(
|
|
657
660
|
resolvedDistPath,
|
|
658
|
-
registryAnswer.registry || undefined
|
|
661
|
+
registryAnswer.registry || undefined,
|
|
662
|
+
isExistingProject
|
|
659
663
|
);
|
|
660
664
|
} catch (error) {
|
|
661
665
|
console.error('\nError during npm publish:', error);
|
|
@@ -760,20 +764,40 @@ async function buildPackage(packagePath: string): Promise<void> {
|
|
|
760
764
|
}
|
|
761
765
|
}
|
|
762
766
|
|
|
763
|
-
async function buildAndPublishPackage(packagePath: string, registry?: string): Promise<void> {
|
|
767
|
+
async function buildAndPublishPackage(packagePath: string, registry?: string, isExistingProject: boolean = false): Promise<void> {
|
|
764
768
|
const packageJsonPath = path.join(packagePath, 'package.json');
|
|
765
769
|
|
|
766
770
|
if (!fs.existsSync(packageJsonPath)) {
|
|
767
771
|
throw new Error(`No package.json found in ${packagePath}`);
|
|
768
772
|
}
|
|
769
773
|
|
|
770
|
-
|
|
774
|
+
let packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
771
775
|
const packageName = packageJson.name;
|
|
772
|
-
|
|
776
|
+
let packageVersion = packageJson.version;
|
|
773
777
|
|
|
774
778
|
// First, ensure dependencies are installed and build the package
|
|
775
779
|
await buildPackage(packagePath);
|
|
776
780
|
|
|
781
|
+
// If this is an existing project, increment the patch version before publishing
|
|
782
|
+
if (isExistingProject) {
|
|
783
|
+
console.log(`\nIncrementing version for existing project...`);
|
|
784
|
+
const versionResult = spawnSync(NPM, ['version', 'patch', '--no-git-tag-version'], {
|
|
785
|
+
cwd: packagePath,
|
|
786
|
+
encoding: 'utf-8',
|
|
787
|
+
stdio: 'pipe'
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
if (versionResult.status !== 0) {
|
|
791
|
+
console.error('Failed to increment version:', versionResult.stderr);
|
|
792
|
+
throw new Error('Failed to increment version');
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Re-read package.json to get the new version
|
|
796
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
797
|
+
packageVersion = packageJson.version;
|
|
798
|
+
console.log(`Version updated to ${packageVersion}`);
|
|
799
|
+
}
|
|
800
|
+
|
|
777
801
|
// Set registry if provided
|
|
778
802
|
if (registry) {
|
|
779
803
|
console.log(`\nUsing npm registry: ${registry}`);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "commonjs",
|
|
4
|
+
"target": "es6",
|
|
5
|
+
"lib": [
|
|
6
|
+
"es6",
|
|
7
|
+
"es7",
|
|
8
|
+
"DOM"
|
|
9
|
+
],
|
|
10
|
+
"allowJs": false,
|
|
11
|
+
"moduleResolution": "node",
|
|
12
|
+
"forceConsistentCasingInFileNames": false,
|
|
13
|
+
"noImplicitReturns": true,
|
|
14
|
+
"noImplicitThis": true,
|
|
15
|
+
"noImplicitAny": true,
|
|
16
|
+
"strictNullChecks": true,
|
|
17
|
+
"noUnusedLocals": false,
|
|
18
|
+
"experimentalDecorators": true,
|
|
19
|
+
"emitDecoratorMetadata": true,
|
|
20
|
+
"esModuleInterop": true,
|
|
21
|
+
"allowSyntheticDefaultImports": true,
|
|
22
|
+
"declaration": false,
|
|
23
|
+
"outDir": "./dist"
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"src/**/*.ts"
|
|
27
|
+
],
|
|
28
|
+
"exclude": [
|
|
29
|
+
]
|
|
30
|
+
}
|