Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,54 @@ Open `http://localhost:3000` β€” inspect the page to see **real HTML elements**,
### 4. Build for Production

```bash
# Build your application
flutterjs build
```

Output is in `dist/` β€” deploy to any static hosting (Netlify, Vercel, GitHub Pages).
This creates a `dist/` directory with:
- `index.html` (Entry point)
- `assets/` (Static resources)
- `main.js` (Compiled app)
- `vercel.json` (Deployment config)

### 5. Deploy to Vercel

Since `flutterjs build` automatically generates `vercel.json`, deployment is zero-config:

```bash
# Using Vercel CLI
vercel deploy
```

Or connect your GitHub repository to Vercelβ€”it will automatically detect the output.

---

## Deployment

### Vercel (Recommended)
Deployment is **zero-config** and optimized for cleanliness (no duplicate `node_modules`).

1. **Build**:
```bash
flutterjs build
```
*(Creates `dist/` with app files, keeps dependencies in root)*

2. **Deploy**:
```bash
cd ./build/flutterjs
vercel deploy --prod
```

The build automatically generates `vercel.json` and `.vercelignore` to ensure:
- **Routing**: SPAs work correctly (all routes β†’ `index.html`)
- **Dependencies**: `node_modules` are uploaded efficiently
- **Cleanliness**: Your project remains unpolluted

### Other Providers
You can deploy the contents of `build/flutterjs/dist/` to any static host (Netlify, GitHub Pages, Firebase Hosting).
*Note: Ensure your provider handles SPA routing (redirect 404s to index.html).*

---

Expand Down Expand Up @@ -206,7 +250,8 @@ Flutter/Dart Source
flutterjs build --mode ssr # Server-side rendering
flutterjs build --mode csr # Client-side rendering (default)
flutterjs build --mode hybrid # Best of both
flutterjs build --no-minify # Skip minification
flutterjs build -O 0 # Debug build (No optimization / No minification)
flutterjs build -O 3 # Production build (Aggressive optimization)
flutterjs build --output ./dist # Custom output directory
```

Expand Down
2 changes: 0 additions & 2 deletions examples/core_lib_demo/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
name: core_lib_demo
description: A new FlutterJS project.
dependencies:
flutterjs:
path: ../../packages/flutterjs
flutterjs_material:
path: ../../packages/flutterjs_material
1 change: 1 addition & 0 deletions examples/counter/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,4 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
.vercel
2 changes: 1 addition & 1 deletion examples/counter/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class _MyHomePageState extends State<MyHomePage> {
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
onPressed: ()=> _incrementCounter(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
Expand Down
2 changes: 1 addition & 1 deletion examples/flutterjs_website
Submodule flutterjs_website updated from ce8abf to dfe40a
5 changes: 2 additions & 3 deletions examples/material_demo/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,8 @@ flutter:
uses-material-design: true

# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
assets:
- assets/images/

# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
Expand Down
1 change: 1 addition & 0 deletions examples/routing_app/debug_main.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ DEBUG: BIN MAIN START
DEBUG: Parsed args. Creating runner...
DEBUG: Runner created
DEBUG: Calling runner.run(args)...
DEBUG: runner.run(args) returned
Original file line number Diff line number Diff line change
Expand Up @@ -994,9 +994,28 @@ class StatementExtractionPass {

// List literals
if (expr is ListLiteral) {
final extractedElements = <ExpressionIR>[];

// DEBUG: Print what elements we're processing
if (expr.elements.isNotEmpty) {
print(
'πŸ” ListLiteral with ${expr.elements.length} elements at ${_extractSourceLocation(expr, expr.offset).line}',
);
for (var i = 0; i < expr.elements.length && i < 5; i++) {
final elem = expr.elements[i];
print(
' Element $i: ${elem.runtimeType} | ${elem.toString().substring(0, elem.toString().length > 60 ? 60 : elem.toString().length)}',
);
}
}

for (final element in expr.elements) {
extractedElements.addAll(_extractCollectionElement(element));
}

return ListExpressionIR(
id: builder.generateId('expr_list'),
elements: expr.elements.map((e) => extractExpression(e)).toList(),
elements: extractedElements,
resultType: SimpleTypeIR(
id: builder.generateId('type'),
name: 'List',
Expand Down Expand Up @@ -1562,6 +1581,174 @@ class StatementExtractionPass {
// Default fallback
return 'unknown_expression';
}

/// Extract collection elements (handles if, spread, for, and regular expressions)
List<ExpressionIR> _extractCollectionElement(dynamic element) {
final sourceLoc = _extractSourceLocation(element, element.offset);

// DEBUG: Print element type
// Check for collection-if FIRST (before Expression check)
// Runtime type is 'IfElementImpl' from Dart analyzer
if (element.runtimeType.toString().contains('IfElementImpl')) {
try {
// Handle different analyzer versions (expression vs condition)
Expression? conditionNode;
try {
conditionNode = (element as dynamic).expression;
} catch (_) {
try {
conditionNode = (element as dynamic).condition;
} catch (_) {
print('⚠️ Could not find condition or expression on IfElementImpl');
}
}

if (conditionNode == null) {
throw Exception('IfElementImpl has no condition/expression property');
}

final thenElement = (element as dynamic).thenElement;
final elseElement = (element as dynamic).elseElement;

final conditionExpr = extractExpression(conditionNode);
final thenElements = _extractCollectionElement(thenElement);
final elseElements = elseElement != null
? _extractCollectionElement(elseElement)
: <ExpressionIR>[];

// Detect spread usage (to force list wrapping/spreading)
final isThenSpread = thenElement.runtimeType.toString().contains(
'SpreadElement',
);
final isElseSpread =
elseElement != null &&
elseElement.runtimeType.toString().contains('SpreadElement');

// Always use spread logic for robustness and consistent JS generation
// wrapping single elements in a list if needed.
ExpressionIR thenExpr;
if (thenElements.length == 1 && isThenSpread) {
// Spread element (single) -> use directly as iterable
thenExpr = thenElements.first;
} else {
// Regular element(s) -> wrap in list
thenExpr = ListExpressionIR(
id: builder.generateId('expr_then_list'),
elements: thenElements,
resultType: SimpleTypeIR(
id: builder.generateId('type'),
name: 'List',
isNullable: false,
sourceLocation: sourceLoc,
),
sourceLocation: sourceLoc,
metadata: {},
);
}

ExpressionIR elseExpr;
if (elseElements.isEmpty) {
elseExpr = ListExpressionIR(
id: builder.generateId('expr_else_empty'),
elements: [],
resultType: SimpleTypeIR(
id: builder.generateId('type'),
name: 'List',
isNullable: false,
sourceLocation: sourceLoc,
),
sourceLocation: sourceLoc,
metadata: {},
);
} else if (elseElements.length == 1 && isElseSpread) {
elseExpr = elseElements.first;
} else {
elseExpr = ListExpressionIR(
id: builder.generateId('expr_else_list'),
elements: elseElements,
resultType: SimpleTypeIR(
id: builder.generateId('type'),
name: 'List',
isNullable: false,
sourceLocation: sourceLoc,
),
sourceLocation: sourceLoc,
metadata: {},
);
}

return [
ConditionalExpressionIR(
id: builder.generateId('expr_cond_spread'),
condition: conditionExpr,
thenExpression: thenExpr,
elseExpression: elseExpr,
resultType: DynamicTypeIR(
id: builder.generateId('type'),
sourceLocation: sourceLoc,
),
sourceLocation: sourceLoc,
metadata: {'fromCollectionIf': true, 'isSpread': true},
),
];
} catch (e) {
return [
UnknownExpressionIR(
id: builder.generateId('expr_if_err'),
source: element.toString(),
sourceLocation: sourceLoc,
metadata: {'error': e.toString()},
),
];
}
}

// Collection-spread: ...expression
// Runtime type is 'SpreadElementImpl' from Dart analyzer
if (element.runtimeType.toString().contains('SpreadElementImpl')) {
try {
final spreadExpr = (element as dynamic).expression;
return [extractExpression(spreadExpr)];
} catch (e) {
return [
UnknownExpressionIR(
id: builder.generateId('expr_spread_err'),
source: element.toString(),
sourceLocation: sourceLoc,
metadata: {'error': e.toString()},
),
];
}
}

// Collection-for: not yet supported
// Runtime type is 'ForElementImpl' from Dart analyzer
if (element.runtimeType.toString().contains('ForElementImpl')) {
return [
UnknownExpressionIR(
id: builder.generateId('expr_for_elem'),
source: element.toString(),
sourceLocation: sourceLoc,
metadata: {'type': 'for-element'},
),
];
}

// Regular expression element (check AFTER collection-specific types)
if (element is Expression) {
return [extractExpression(element)];
}

// Unknown
return [
UnknownExpressionIR(
id: builder.generateId('expr_unknown_coll'),
source: element.toString(),
sourceLocation: sourceLoc,
metadata: {},
),
];
}
}

/// Extension for ForStatement helper
Expand Down
2 changes: 2 additions & 0 deletions packages/flutterjs_engine/bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,13 @@ program
.option('--sourcemap', 'Generate source maps')
.option('--minify', 'Minify output', true)
.option('--no-minify', 'Disable minification')
.option('--to-js', 'Generate JavaScript output only (internal use)')
.action(async (options) => {
try {
const globalOpts = program.opts();
const projectContext = loadProjectContext(globalOpts.config);
await build({ ...options, ...globalOpts }, projectContext);
process.exit(0);
} catch (error) {
handleError(error, program.opts());
}
Expand Down
Loading
Loading