A revolutionary cross-platform widget system that enables Flutter widgets to be seamlessly integrated into React, Vue.js, and Flutter applications. This project provides a universal abstraction layer that allows developers to write Flutter widgets once and use them across multiple frontend frameworks.
- Architecture Overview
- Usage Instructions
- Build and Publish Process
- Development Guide
- Contribution Guidelines
- Troubleshooting
- License
The Flutter Universal Widget system is built on a multi-layered architecture that enables Flutter widgets to run in web environments through a sophisticated abstraction layer.
graph TB
subgraph "Frontend Frameworks"
React[React Application]
Vue[Vue.js Application]
Flutter[Flutter Application]
end
subgraph "Universal Widget Libraries"
ReactLib[fuw-react-lib<br/>NPM Package]
VueLib[fuw-vue-lib<br/>NPM Package]
FlutterPkg[flutter_universal_widget<br/>Pub.dev Package]
end
subgraph "Flutter Web Engine"
FlutterWeb[Flutter Web App<br/>Multi-View Support]
MultiViewApp[MultiViewApp<br/>View Manager]
WidgetMapper[WidgetMapper<br/>Data Bridge]
FlutterEngine[Flutter Engine<br/>CanvasKit/Skwasm]
end
subgraph "CDN Distribution"
CDN[CDN Assets<br/>jsdelivr.net]
Bootstrap[bootstrap.js]
MainDart[main.dart.js]
Assets[Flutter Assets]
end
React --> ReactLib
Vue --> VueLib
Flutter --> FlutterPkg
ReactLib --> CDN
VueLib --> CDN
CDN --> Bootstrap
CDN --> MainDart
CDN --> Assets
Bootstrap --> FlutterEngine
MainDart --> FlutterWeb
FlutterWeb --> MultiViewApp
MultiViewApp --> WidgetMapper
WidgetMapper --> FlutterPkg
style React fill:#61dafb
style Vue fill:#4fc08d
style Flutter fill:#02569b
style FlutterEngine fill:#ff6b6b
style CDN fill:#ffd93d
sequenceDiagram
participant App as Frontend App
participant Wrapper as FlutterWidget Wrapper
participant CDN as CDN Assets
participant Engine as Flutter Engine
participant MultiView as MultiViewApp
participant Widget as Flutter Widget
App->>Wrapper: Initialize FlutterWidget(greeting: "Hello")
Wrapper->>CDN: Load bootstrap.js
CDN-->>Wrapper: Bootstrap loaded
Wrapper->>Engine: Initialize with multiViewEnabled: true
Engine-->>Wrapper: Engine ready
Wrapper->>MultiView: runApp()
MultiView-->>Wrapper: App instance
Wrapper->>MultiView: addView({hostElement, initialData})
MultiView->>Widget: Create view with greeting data
Widget-->>MultiView: Render widget
MultiView-->>Wrapper: Return viewId
Note over App,Widget: Widget is now rendered in DOM
App->>Wrapper: Update greeting prop
Wrapper->>MultiView: removeView(oldViewId)
Wrapper->>MultiView: addView({hostElement, newData})
MultiView->>Widget: Re-render with new data
Widget-->>MultiView: Updated widget
MultiView-->>Wrapper: New viewId
- Flutter Package (
flutter_universal_widget): Core Flutter widget definitions - React Wrapper (
fuw-react-lib): React component that embeds Flutter widgets - Vue Wrapper (
fuw-vue-lib): Vue.js component that embeds Flutter widgets
- MultiViewApp: Manages multiple Flutter widget instances in a single Flutter app
- WidgetMapper: Bridges data between JavaScript and Dart environments
- View Management: Handles creation, updates, and disposal of widget views
- Bootstrap Loading: Dynamic loading of Flutter engine assets
- Asset Management: Efficient delivery of Flutter web assets via CDN
- Version Management: Automatic version resolution for package assets
The Flutter Universal Widget system provides seamless integration across React, Vue.js, and Flutter applications. Each platform has its own wrapper library that handles the complexity of embedding Flutter widgets.
npm install fuw-react-lib
# or
yarn add fuw-react-lib
# or
pnpm add fuw-react-libimport React from 'react';
import FlutterWidget from 'fuw-react-lib';
function App() {
return (
<div className="app">
<h1>My React App</h1>
<FlutterWidget
greeting="Hello from React!"
height={400}
className="my-flutter-widget"
style={{ border: '1px solid #ccc' }}
/>
</div>
);
}
export default App;import React, { useState } from 'react';
import FlutterWidget, { FlutterWidgetProps } from 'fuw-react-lib';
function AdvancedExample() {
const [greeting, setGreeting] = useState('Hello World!');
const widgetProps: FlutterWidgetProps = {
greeting,
height: 500,
renderer: 'canvaskit', // or 'skwasm'
className: 'custom-flutter-widget',
style: {
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
}
};
return (
<div>
<input
value={greeting}
onChange={(e) => setGreeting(e.target.value)}
placeholder="Enter greeting"
/>
<FlutterWidget {...widgetProps} />
</div>
);
}The React library includes full TypeScript definitions:
export interface FlutterWidgetProps {
greeting: string;
height?: number;
className?: string;
style?: React.CSSProperties;
assetBase?: string;
renderer?: "skwasm" | "canvaskit";
}npm install fuw-vue-lib
# or
yarn add fuw-vue-lib
# or
pnpm add fuw-vue-lib<template>
<div class="app">
<h1>My Vue.js App</h1>
<FlutterWidget
:greeting="greeting"
:height="400"
class="my-flutter-widget"
:style="{ border: '1px solid #ccc' }"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { FlutterWidget } from 'fuw-vue-lib';
const greeting = ref('Hello from Vue.js!');
</script><template>
<div>
<input
v-model="greeting"
placeholder="Enter greeting"
/>
<FlutterWidget
:greeting="greeting"
:height="height"
:renderer="renderer"
class="custom-flutter-widget"
:style="widgetStyle"
/>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { FlutterWidget } from 'fuw-vue-lib';
const greeting = ref('Hello World!');
const height = ref(500);
const renderer = ref<'canvaskit' | 'skwasm'>('canvaskit');
const widgetStyle = computed(() => ({
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
}));
</script><template>
<div>
<FlutterWidget
:greeting="greeting"
:height="400"
renderer="canvaskit"
/>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { FlutterWidget } from 'fuw-vue-lib';
export default defineComponent({
components: {
FlutterWidget
},
data() {
return {
greeting: 'Hello from Vue Options API!'
};
}
});
</script>Add the package to your pubspec.yaml:
dependencies:
flutter_universal_widget: ^0.0.1Then run:
flutter pub getimport 'package:flutter/material.dart';
import 'package:flutter_universal_widget/flutter_universal_widget.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Flutter Universal Widget')),
body: Center(
child: FlutterWidget(
greeting: 'Hello from Flutter!',
),
),
),
);
}
}import 'package:flutter/material.dart';
import 'package:flutter_universal_widget/flutter_universal_widget.dart';
class CustomFlutterWidget extends StatelessWidget {
final String greeting;
final Color? backgroundColor;
final TextStyle? textStyle;
const CustomFlutterWidget({
Key? key,
required this.greeting,
this.backgroundColor,
this.textStyle,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: backgroundColor ?? Colors.blue.shade100,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
child: Text(
greeting,
style: textStyle ?? TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.blue.shade800,
),
textAlign: TextAlign.center,
),
);
}
}import 'package:flutter/material.dart';
import 'package:flutter_universal_widget/flutter_universal_widget.dart';
import 'package:provider/provider.dart';
class GreetingProvider extends ChangeNotifier {
String _greeting = 'Hello World!';
String get greeting => _greeting;
void updateGreeting(String newGreeting) {
_greeting = newGreeting;
notifyListeners();
}
}
class StatefulFlutterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => GreetingProvider(),
child: Consumer<GreetingProvider>(
builder: (context, provider, child) {
return Column(
children: [
TextField(
onChanged: provider.updateGreeting,
decoration: InputDecoration(
labelText: 'Enter greeting',
),
),
SizedBox(height: 16),
FlutterWidget(greeting: provider.greeting),
],
);
},
),
);
}
}All platform wrappers support the following configuration options:
| Property | Type | Default | Description |
|---|---|---|---|
greeting |
string |
Required | The text to display in the widget |
height |
number |
420 |
Height of the widget container in pixels |
renderer |
'canvaskit' | 'skwasm' |
'canvaskit' |
Flutter web renderer to use |
assetBase |
string |
CDN URL | Custom base URL for Flutter assets |
className |
string |
undefined |
CSS class for styling (React only) |
class |
string |
undefined |
CSS class for styling (Vue only) |
style |
object |
{} |
Inline styles for the container |
The Flutter Universal Widget system uses a sophisticated build pipeline that coordinates the compilation and distribution of Flutter, React, and Vue.js components.
graph LR
subgraph "Development"
FlutterSrc[Flutter Package<br/>Source Code]
ReactSrc[React Wrapper<br/>Source Code]
VueSrc[Vue Wrapper<br/>Source Code]
WebApp[Flutter Web App<br/>Source Code]
end
subgraph "Build Process"
FlutterBuild[Flutter Build<br/>Web Assets]
ReactBuild[React Library<br/>Build]
VueBuild[Vue Library<br/>Build]
Makefile[Makefile<br/>Orchestration]
end
subgraph "Distribution"
PubDev[pub.dev<br/>Flutter Package]
NPMReact[NPM<br/>fuw-react-lib]
NPMVue[NPM<br/>fuw-vue-lib]
CDNAssets[CDN<br/>Flutter Assets]
end
FlutterSrc --> Makefile
ReactSrc --> Makefile
VueSrc --> Makefile
WebApp --> Makefile
Makefile --> FlutterBuild
Makefile --> ReactBuild
Makefile --> VueBuild
FlutterBuild --> CDNAssets
ReactBuild --> NPMReact
VueBuild --> NPMVue
FlutterSrc --> PubDev
NPMReact --> CDNAssets
NPMVue --> CDNAssets
style Makefile fill:#ff9999
style PubDev fill:#02569b
style NPMReact fill:#cb3837
style NPMVue fill:#cb3837
style CDNAssets fill:#ffd93d
- Flutter SDK (>=3.9.0)
- Node.js (>=18.12.0)
- pnpm (recommended) or npm/yarn
# Clean and build all components
make buildThis command performs the following steps:
- Clean: Removes all build artifacts and dependencies
- Flutter Analysis: Runs
flutter analyzeon the Flutter package - Flutter Tests: Executes
flutter testin the package directory - Flutter Web Build: Compiles the Flutter web app with debug symbols
- React Library Build: Builds the React wrapper using Rslib
- Vue Library Build: Builds the Vue.js wrapper using Rslib
# Build only Flutter package
cd package && flutter test
# Build only React library
cd web/react-lib && pnpm build
# Build only Vue library
cd web/vue-lib && pnpm build
# Build only Flutter web app
cd web/_flutter && flutter build web --debug --pwa-strategy=none-
Update Version:
cd web/react-lib npm version patch|minor|major
-
Build and Test:
pnpm build pnpm check # Run linting and formatting -
Publish:
npm publish
-
Update Version:
cd web/vue-lib npm version patch|minor|major
-
Build and Test:
pnpm build pnpm check # Run linting and formatting -
Publish:
npm publish
-
Update Version:
# In package/pubspec.yaml version: 0.0.2 # Increment version
-
Validate Package:
cd package flutter pub publish --dry-run -
Publish:
flutter pub publish
This project follows Semantic Versioning:
- MAJOR: Breaking changes to the API
- MINOR: New features that are backward compatible
- PATCH: Bug fixes and minor improvements
-
Update all package versions consistently across:
package/pubspec.yaml(Flutter package)web/react-lib/package.json(React library)web/vue-lib/package.json(Vue library)
-
Run full build and tests:
make build
-
Update documentation if needed
-
Create release commit:
git add . git commit -m "release: v1.0.0" git tag v1.0.0 git push origin main --tags
-
Publish packages in order:
- Flutter package to pub.dev
- React library to NPM
- Vue library to NPM
The project uses GitHub Actions for automated testing and deployment:
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: make build
publish:
needs: test
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Publish to NPM
run: |
cd web/react-lib && npm publish
cd web/vue-lib && npm publish
- name: Publish to pub.dev
run: cd package && flutter pub publish --forceThis section provides comprehensive guidance for developers who want to contribute to or extend the Flutter Universal Widget system.
flutter_universal_widget/
├── package/ # Flutter package source
│ ├── lib/
│ │ ├── flutter_universal_widget.dart
│ │ └── src/
│ │ └── flutter_widget.dart
│ ├── test/
│ └── pubspec.yaml
├── web/
│ ├── _flutter/ # Flutter web application
│ │ ├── lib/
│ │ │ ├── main.dart
│ │ │ ├── multi_view_app.dart
│ │ │ └── src/
│ │ │ └── widget_view.dart
│ │ └── pubspec.yaml
│ ├── react-lib/ # React wrapper library
│ │ ├── src/
│ │ │ └── index.tsx
│ │ ├── package.json
│ │ └── rslib.config.ts
│ └── vue-lib/ # Vue.js wrapper library
│ ├── src/
│ │ ├── FlutterWidget.vue
│ │ └── index.ts
│ ├── package.json
│ └── rslib.config.ts
├── demos/
│ └── react-example/ # React demo application
├── Makefile # Build orchestration
└── pubspec.yaml # Workspace configuration
git clone https://github.com/your-username/flutter_universal_widget.git
cd flutter_universal_widget# Install Flutter dependencies
flutter pub get
# Install Node.js dependencies for React library
cd web/react-lib
pnpm install
# Install Node.js dependencies for Vue library
cd ../vue-lib
pnpm install
# Install dependencies for React demo
cd ../../demos/react-example
pnpm install# Run full build to verify everything works
make build// package/lib/src/my_custom_widget.dart
import 'package:flutter/material.dart';
class MyCustomWidget extends StatelessWidget {
final String title;
final String description;
final VoidCallback? onTap;
const MyCustomWidget({
Key? key,
required this.title,
required this.description,
this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
child: InkWell(
onTap: onTap,
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.headlineSmall,
),
SizedBox(height: 8),
Text(
description,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
),
);
}
}// web/react-lib/src/MyCustomWidget.tsx
import React, { useEffect, useState } from "react";
export interface MyCustomWidgetProps {
title: string;
description: string;
onTap?: () => void;
height?: number;
className?: string;
style?: React.CSSProperties;
renderer?: "skwasm" | "canvaskit";
}
export function MyCustomWidget({
title,
description,
onTap,
height = 200,
className,
style,
renderer,
}: MyCustomWidgetProps) {
const id = `my-custom-widget-${Math.random().toString(36).substring(7)}`;
const [viewId, setViewId] = useState<number | null>(null);
useEffect(() => {
let cancelled = false;
ensureApp(renderer).then((app) => {
if (cancelled) return;
if (viewId != null) app.removeView(viewId);
setViewId(app.addView({
hostElement: document.querySelector('#' + id),
initialData: { title, description, onTap: onTap?.toString() },
viewConstraints: {
minWidth: 300,
maxWidth: Infinity,
minHeight: height,
maxHeight: height
},
}));
});
return () => {
cancelled = true;
if (appInstance && viewId != null) {
appInstance.removeView(viewId);
setViewId(null);
}
};
}, [title, description, onTap, renderer, height]);
return (
<div
id={id}
className={className}
style={{ width: "100%", height, ...style }}
/>
);
}
export default MyCustomWidget;<!-- web/vue-lib/src/MyCustomWidget.vue -->
<template>
<div
ref="host"
:class="klass"
:style="[{ width: '100%', height: `${height}px` }, style]"
/>
</template>
<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref, watch, computed } from 'vue';
interface Props {
title: string;
description: string;
onTap?: () => void;
height?: number;
renderer?: 'skwasm' | 'canvaskit';
class?: string;
style?: Record<string, string | number>;
}
const props = defineProps<Props>();
const height = computed(() => props.height ?? 200);
const klass = computed(() => props.class);
const host = ref<HTMLElement | null>(null);
let app: any | null = null;
let viewId: number | null = null;
async function mountOrRemountView() {
const a = await ensureApp(props.renderer);
if (!host.value) return;
if (viewId != null) a.removeView(viewId);
viewId = a.addView({
hostElement: host.value,
initialData: {
title: props.title,
description: props.description,
onTap: props.onTap?.toString()
},
viewConstraints: { maxWidth: Infinity, maxHeight: Infinity },
});
}
onMounted(mountOrRemountView);
watch(() => [props.title, props.description, props.onTap], () => {
void mountOrRemountView();
});
onBeforeUnmount(() => {
if (app && viewId != null) {
app.removeView(viewId);
viewId = null;
}
});
</script>// package/test/flutter_universal_widget_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_universal_widget/flutter_universal_widget.dart';
void main() {
group('FlutterWidget', () {
testWidgets('displays greeting text', (WidgetTester tester) async {
const greeting = 'Hello, Test!';
await tester.pumpWidget(
MaterialApp(
home: FlutterWidget(greeting: greeting),
),
);
expect(find.text(greeting), findsOneWidget);
});
testWidgets('updates when greeting changes', (WidgetTester tester) async {
String greeting = 'Initial greeting';
await tester.pumpWidget(
MaterialApp(
home: StatefulBuilder(
builder: (context, setState) {
return Column(
children: [
FlutterWidget(greeting: greeting),
ElevatedButton(
onPressed: () {
setState(() {
greeting = 'Updated greeting';
});
},
child: Text('Update'),
),
],
);
},
),
),
);
expect(find.text('Initial greeting'), findsOneWidget);
await tester.tap(find.text('Update'));
await tester.pump();
expect(find.text('Updated greeting'), findsOneWidget);
expect(find.text('Initial greeting'), findsNothing);
});
});
}// web/react-lib/src/__tests__/FlutterWidget.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import FlutterWidget from '../index';
// Mock the Flutter engine
jest.mock('../flutter-engine', () => ({
ensureApp: jest.fn().mockResolvedValue({
addView: jest.fn().mockReturnValue(1),
removeView: jest.fn(),
}),
}));
describe('FlutterWidget', () => {
test('renders with correct props', () => {
render(
<FlutterWidget
greeting="Test greeting"
height={300}
className="test-class"
/>
);
const widget = screen.getByRole('generic');
expect(widget).toHaveClass('test-class');
expect(widget).toHaveStyle({ height: '300px' });
});
test('updates when props change', () => {
const { rerender } = render(
<FlutterWidget greeting="Initial" />
);
rerender(<FlutterWidget greeting="Updated" />);
// Verify that the widget re-renders with new props
// (specific assertions depend on your implementation)
});
});// web/vue-lib/src/__tests__/FlutterWidget.spec.ts
import { mount } from '@vue/test-utils';
import FlutterWidget from '../FlutterWidget.vue';
// Mock the Flutter engine
vi.mock('../flutter-engine', () => ({
ensureApp: vi.fn().mockResolvedValue({
addView: vi.fn().mockReturnValue(1),
removeView: vi.fn(),
}),
}));
describe('FlutterWidget', () => {
test('renders with correct props', () => {
const wrapper = mount(FlutterWidget, {
props: {
greeting: 'Test greeting',
height: 300,
class: 'test-class',
},
});
expect(wrapper.classes()).toContain('test-class');
expect(wrapper.element.style.height).toBe('300px');
});
test('updates when props change', async () => {
const wrapper = mount(FlutterWidget, {
props: {
greeting: 'Initial',
},
});
await wrapper.setProps({ greeting: 'Updated' });
// Verify that the widget re-renders with new props
// (specific assertions depend on your implementation)
});
});# Run all tests
make test
# Run Flutter tests only
cd package && flutter test
# Run React tests only
cd web/react-lib && npm test
# Run Vue tests only
cd web/vue-lib && npm test
# Run demo application tests
cd demos/react-example && npm test- Flutter: Follow Effective Dart guidelines
- React: Use TypeScript, functional components, and hooks
- Vue.js: Use Composition API with TypeScript
- General: Use consistent naming conventions across all platforms
- Lazy Loading: Flutter engine is loaded only when first widget is rendered
- View Reuse: Minimize view creation/destruction by reusing views when possible
- Memory Management: Properly dispose of views when components unmount
- Asset Optimization: Use CDN for efficient asset delivery
// Example error handling in React wrapper
useEffect(() => {
let cancelled = false;
ensureApp(renderer)
.then((app) => {
if (cancelled) return;
// ... widget logic
})
.catch((error) => {
console.error('Failed to initialize Flutter widget:', error);
// Handle error appropriately
});
return () => {
cancelled = true;
// ... cleanup logic
};
}, [/* dependencies */]);We welcome contributions to the Flutter Universal Widget project! This section outlines the process for contributing and the standards we maintain.
This project follows Angular-style commit message conventions for consistent and meaningful commit history.
<type>(<scope>): <subject>
<body>
<footer>
- feat: A new feature
- fix: A bug fix
- docs: Documentation only changes
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
- refactor: A code change that neither fixes a bug nor adds a feature
- perf: A code change that improves performance
- test: Adding missing tests or correcting existing tests
- build: Changes that affect the build system or external dependencies
- ci: Changes to our CI configuration files and scripts
- chore: Other changes that don't modify src or test files
- revert: Reverts a previous commit
- flutter: Changes to the Flutter package
- react: Changes to the React wrapper library
- vue: Changes to the Vue.js wrapper library
- web: Changes to the Flutter web application
- demo: Changes to demo applications
- build: Changes to build system or dependencies
- docs: Changes to documentation
feat(react): add support for custom renderers
Add support for specifying custom Flutter renderers in React components.
This allows developers to choose between CanvasKit and Skwasm renderers
based on their performance requirements.
Closes #123
fix(flutter): resolve widget disposal memory leak
Fixed an issue where Flutter widgets were not properly disposed when
removed from the widget tree, causing memory leaks in long-running
applications.
docs(readme): update installation instructions
Updated the installation section to include pnpm as the recommended
package manager and added troubleshooting steps for common issues.
test(vue): add comprehensive component tests
Added unit tests for Vue.js wrapper component covering:
- Props validation
- Event handling
- Lifecycle management
- Error scenarios# Fork the repository on GitHub
git clone https://github.com/your-username/flutter_universal_widget.git
cd flutter_universal_widget
# Create a feature branch
git checkout -b feat/your-feature-name- Follow the coding standards for each platform
- Write tests for new functionality
- Update documentation as needed
- Ensure all existing tests pass
# Run full test suite
make build
# Run platform-specific tests
cd package && flutter test
cd web/react-lib && npm test
cd web/vue-lib && npm test# Stage your changes
git add .
# Commit with conventional message
git commit -m "feat(react): add custom renderer support"
# Push to your fork
git push origin feat/your-feature-name- Create a pull request from your fork to the main repository
- Use a descriptive title following commit conventions
- Include a detailed description of changes
- Reference any related issues
- Add screenshots or demos if applicable
- Functionality: Does the code work as intended?
- Testing: Are there adequate tests for the changes?
- Documentation: Is documentation updated appropriately?
- Code Quality: Does the code follow project standards?
- Performance: Are there any performance implications?
- Compatibility: Does it maintain backward compatibility?
- Initial review within 48 hours
- Follow-up reviews within 24 hours
- Approval requires at least one maintainer review
- Complex changes may require multiple reviewer approvals
// Use descriptive names
class FlutterUniversalWidget extends StatelessWidget {
final String greeting;
final TextStyle? textStyle;
const FlutterUniversalWidget({
Key? key,
required this.greeting,
this.textStyle,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16.0),
child: Text(
greeting,
style: textStyle ?? Theme.of(context).textTheme.bodyLarge,
),
);
}
}Standards:
- Use
constconstructors where possible - Follow
lowerCamelCasefor variables and methods - Use
UpperCamelCasefor classes and enums - Add comprehensive documentation comments
- Use
finalfor immutable variables
// Use functional components with TypeScript
interface FlutterWidgetProps {
greeting: string;
height?: number;
onError?: (error: Error) => void;
}
export const FlutterWidget: React.FC<FlutterWidgetProps> = ({
greeting,
height = 420,
onError,
}) => {
const [isLoading, setIsLoading] = useState(true);
const handleError = useCallback((error: Error) => {
console.error('Flutter widget error:', error);
onError?.(error);
}, [onError]);
return (
<div
className="flutter-widget-container"
style={{ height }}
role="application"
aria-label="Flutter widget"
>
{/* Widget content */}
</div>
);
};Standards:
- Use functional components with hooks
- Provide comprehensive TypeScript interfaces
- Use
useCallbackanduseMemofor optimization - Include accessibility attributes
- Handle errors gracefully
<template>
<div
ref="container"
class="flutter-widget-container"
:style="{ height: `${height}px` }"
role="application"
:aria-label="ariaLabel"
>
<!-- Widget content -->
</div>
</template>
<script setup lang="ts">
interface Props {
greeting: string;
height?: number;
ariaLabel?: string;
}
const props = withDefaults(defineProps<Props>(), {
height: 420,
ariaLabel: 'Flutter widget',
});
const emit = defineEmits<{
error: [error: Error];
loaded: [];
}>();
const container = ref<HTMLElement>();
// Use computed for reactive values
const containerStyle = computed(() => ({
height: `${props.height}px`,
}));
</script>Standards:
- Use Composition API with
<script setup> - Provide comprehensive TypeScript interfaces
- Use
withDefaultsfor prop defaults - Include accessibility attributes
- Emit events for parent communication
Use the following template for bug reports:
**Bug Description**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected Behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment:**
- Platform: [React/Vue.js/Flutter]
- Package Version: [e.g. 1.0.0]
- Browser: [e.g. Chrome 91]
- OS: [e.g. macOS 12.0]
**Additional Context**
Add any other context about the problem here.Use the following template for feature requests:
**Feature Description**
A clear and concise description of what you want to happen.
**Use Case**
Describe the use case that would benefit from this feature.
**Proposed Solution**
A clear and concise description of what you want to happen.
**Alternatives Considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional Context**
Add any other context or screenshots about the feature request here.Problem: Flutter widget doesn't render or shows blank space
Solutions:
- Check browser console for JavaScript errors
- Verify CDN assets are accessible:
// Test in browser console fetch('https://cdn.jsdelivr.net/npm/fuw-react-lib@latest/flutter/bootstrap.js') .then(response => console.log('CDN accessible:', response.ok)) .catch(error => console.error('CDN error:', error));
- Try different renderer:
<FlutterWidget greeting="Hello" renderer="skwasm" />
Problem: Application becomes slow after multiple widget updates
Solutions:
-
Ensure proper cleanup in React:
useEffect(() => { return () => { if (appInstance && viewId != null) { appInstance.removeView(viewId); } }; }, []);
-
Ensure proper cleanup in Vue:
<script setup> onBeforeUnmount(() => { if (app && viewId != null) { app.removeView(viewId); viewId = null; } }); </script>
Problem: Build fails with dependency conflicts
Solutions:
-
Clear all caches:
flutter clean rm -rf web/react-lib/node_modules rm -rf web/vue-lib/node_modules rm -rf demos/react-example/node_modules
-
Reinstall dependencies:
flutter pub get cd web/react-lib && pnpm install cd ../vue-lib && pnpm install cd ../../demos/react-example && pnpm install
Problem: TypeScript compilation errors in React/Vue libraries
Solutions:
-
Update TypeScript definitions:
cd web/react-lib && pnpm add -D @types/react@latest cd ../vue-lib && pnpm add -D vue-tsc@latest
-
Check
tsconfig.jsonconfiguration matches project requirements
Problem: Slow widget rendering or updates
Solutions:
-
Use CanvasKit renderer for better performance:
<FlutterWidget greeting="Hello" renderer="canvaskit" />
-
Minimize widget re-creation by using stable keys:
<FlutterWidget key="stable-key" greeting={greeting} />
-
Implement proper memoization:
const MemoizedFlutterWidget = React.memo(FlutterWidget);
| Browser | Version | React Support | Vue Support | Notes |
|---|---|---|---|---|
| Chrome | 88+ | ✅ | ✅ | Full support |
| Firefox | 85+ | ✅ | ✅ | Full support |
| Safari | 14+ | ✅ | ✅ | Full support |
| Edge | 88+ | ✅ | ✅ | Full support |
| Mobile Safari | 14+ | ✅ | ✅ | Performance may vary |
| Chrome Mobile | 88+ | ✅ | ✅ | Performance may vary |
- Check existing issues: Search GitHub Issues
- Create new issue: Use appropriate template for bugs or feature requests
- Community discussions: Join discussions in GitHub Discussions
- Documentation: Review this README and inline code documentation
This project is licensed under the MIT License - see the LICENSE file for details.
- ✅ Commercial use
- ✅ Modification
- ✅ Distribution
- ✅ Private use
- ❌ Liability
- ❌ Warranty
This project uses several third-party libraries:
- Flutter SDK: BSD-3-Clause License
- React: MIT License
- Vue.js: MIT License
- TypeScript: Apache-2.0 License
- Flutter team for the amazing framework and web support
- React and Vue.js communities for excellent tooling
- Contributors who help improve this project
Made with ❤️ by the Flutter Universal Widget team