The WebF CLI is a code generation tool that creates type-safe bindings between Flutter/Dart and JavaScript frameworks (React, Vue). It analyzes TypeScript definition files and generates corresponding Dart classes and JavaScript/TypeScript components.
analyzer.ts- TypeScript AST analysis with multi-level cachinggenerator.ts- Orchestrates code generation for Dart, React, and Vuedart.ts- Dart code generation from TypeScript definitionsreact.ts- React component generationvue.ts- Vue component generationcommands.ts- CLI command implementationslogger.ts- Logging utility without external dependencies
- Multi-level caching for performance optimization
- Parallel file processing
- Type-safe attribute handling with automatic conversions
- Comprehensive error handling and recovery
// Boolean attributes are always non-nullable in Dart
interface Props {
open?: boolean; // Generates: bool get open;
title?: string; // Generates: String? get title;
}HTML attributes are always strings, so the generator includes automatic type conversion:
- Boolean:
value == 'true' || value == '' - Integer:
int.tryParse(value) ?? 0 - Double:
double.tryParse(value) ?? 0.0 - String: Direct assignment
- Source File Cache: Parsed TypeScript AST files are cached
- Type Conversion Cache: Frequently used type conversions are cached
- File Content Cache: File contents are cached to detect changes
Files are processed in batches for optimal parallelism:
await processFilesInBatch(items, batchSize, processor);- Unit tests for all core modules
- Mock file system operations before module imports
- Test coverage threshold: 70%
npm test # Run all tests
npm test -- test/analyzer.test.ts # Run specific test
npm run test:coverage # Run with coverage reportFor modules that read files at load time:
jest.mock('fs');
import fs from 'fs';
const mockFs = fs as jest.Mocked<typeof fs>;
mockFs.readFileSync = jest.fn().mockImplementation((path) => {
// Return appropriate content
});
// Now import the module
import { moduleUnderTest } from './module';# Generate code from TypeScript definitions (auto-creates project if needed)
webf codegen <output-dir> --flutter-package-src=<path> [--framework=react|vue] [--package-name=<name>] [--publish-to-npm] [--npm-registry=<url>]
# Create a new project without code generation
webf codegen <output-dir> [--framework=react|vue] [--package-name=<name>]
# Generate and publish to npm
webf codegen <output-dir> --flutter-package-src=<path> --publish-to-npm
# Generate and publish to custom registry
webf codegen <output-dir> --flutter-package-src=<path> --publish-to-npm --npm-registry=https://custom.registry.com/The generate command now automatically detects if a project needs to be created:
- If required files (package.json, global.d.ts, tsconfig.json) are missing, it will create a new project
- If framework or package name are not provided, it will prompt interactively
- If an existing project is detected, it will use the existing configuration
- Framework can be auto-detected from existing package.json dependencies
When creating typing projects, the CLI automatically synchronizes metadata from the Flutter package:
- Reads
pubspec.yamlfrom the Flutter package source directory - Extracts version and description information
- Applies this metadata to the generated
package.jsonfiles - Ensures typing packages match the same version as the Flutter package
After code generation, the CLI automatically runs npm run build if a build script is present in the package.json. This ensures the generated package is immediately ready for use or publishing. The build process:
- Checks for the presence of a
buildscript in package.json - Runs the build command if available
- Continues successfully even if the build fails (with a warning)
- Provides clear console output about the build status
The CLI supports automatic npm publishing with the following features:
- --publish-to-npm: Automatically publishes the generated package to npm (build is run automatically)
- --npm-registry: Specify a custom npm registry URL (defaults to https://registry.npmjs.org/)
- Interactive publishing: If not using the --publish-to-npm flag, the CLI will ask if you want to publish after generation
- Registry configuration: When choosing to publish interactively, you can specify a custom registry URL
- Checks if user is logged in before attempting to publish
- Temporarily sets and resets registry configuration when custom registry is used
Requirements for publishing:
- Must be logged in to npm (
npm login) - Package must have a valid package.json
- Package will be built automatically before publishing (if build script exists)
Publishing workflow:
- If
--publish-to-npmis not specified, CLI prompts after successful generation - If user chooses to publish, CLI asks for registry URL (optional)
- Validates npm login status
- Publishes to specified registry (no need to build separately)
- Dart files are generated in the Flutter package source directory
- React/Vue files are generated in the specified output directory
- If no output directory is specified, a temporary directory is created
- Temporary directories are created in the system temp folder with prefix
webf-typings- - When using temporary directories, the path is displayed at the end of generation
- Paths can be absolute or relative to current working directory
When running dartGen:
- Dart binding files are generated with
_bindings_generated.dartsuffix - Original
.d.tsfiles are copied to the output directory maintaining their structure - Directory structure from source is preserved in the output
- Update TypeScript interfaces/types
- Implement feature with tests
- Update templates if needed
- Ensure all tests pass
- Update this documentation
Use the logger for debugging:
import { debug, info, warn, error } from './logger';
debug('Processing file:', filename);Templates are in /templates/*.tpl. When modifying:
- Update the template file
- Update the corresponding generator function
- Ensure generated code follows style guidelines
Solution: Use generateAttributeSetter which handles type conversion
Solution: Check for literal types containing null:
if (type.kind === ts.SyntaxKind.LiteralType) {
const literalType = type as ts.LiteralTypeNode;
if (literalType.literal.kind === ts.SyntaxKind.NullKeyword) {
return FunctionArgumentType.null;
}
}Solution: Clear caches before generation:
clearCaches();- Use async/await for asynchronous operations
- Implement proper error handling with try-catch
- Add descriptive error messages
- Use TypeScript strict mode
- Follow existing patterns in the codebase