A powerful Dart package that brings C-style macro preprocessing capabilities to Dart, enabling compile-time code generation and manipulation.
Add this to your package's pubspec.yaml file:
dependencies:
dart_macros: ^1.0.2
Install it:
dart pub get
dart_macros provides a familiar C-like macro system for Dart developers, offering features such as:
- β Object-like macros for constant definitions
- β Function-like macros for code generation
- β Token concatenation operations
- β Conditional compilation directives
- β Macro expansion and evaluation
- β Built-in predefined macros
- β Cross-platform support (Flutter/iOS/Android)
- Code generation without external build tools
- Platform-specific code branching
- Debug and release mode configurations
- Compile-time constants and computations
- Code reusability through macro templates
- Meta-programming capabilities
- White-label applications with client-specific configurations
- π Clean, lightweight syntax that feels natural in Dart
- π Type-safe macro expansions
- π₯ Detailed error reporting and debugging support
- π Integration with existing Dart tooling
- β‘ Performance optimization through compile-time evaluation
- π§© Support for nested macro definitions
- π± Full support for Flutter on all platforms
Simple macros that define constants or expressions:
import 'package:dart_macros/dart_macros.dart';
// Definition
@MacroFile()
@Define('MAX_SIZE', 100)
@Define('PI', 3.14159)
@Define('DEBUG', true)
void main() async {
await initializeDartMacros(); // This is optional but won't hurt
// Usage
var array = List<int>.filled(Macros.get<int>('MAX_SIZE'), 0);
var circleArea = Macros.get<double>('PI') * radius * radius;
if (Macros.get<bool>('DEBUG')) {
print('Debug mode enabled');
}
}
Macros that take parameters and expand to code:
import 'package:dart_macros/dart_macros.dart';
@MacroFile()
@DefineMacro(
'SQUARE',
'x * x',
parameters: ['x'],
)
@DefineMacro(
'MIN',
'a < b ? a : b',
parameters: ['a', 'b'],
)
@DefineMacro(
'VALIDATE',
'x >= 0 && x <= max',
parameters: ['x', 'max'],
)
void main() async {
await initializeDartMacros();
// Usage
var squared = MacroFunctions.SQUARE(5); // Evaluates to 25
var minimum = MacroFunctions.MIN(x, y); // Returns the smaller of x and y
var isValid = MacroFunctions.VALIDATE(value, 100); // Checks if value is in range
}
Use dart_macros in Flutter applications on all platforms:
import 'package:flutter/material.dart';
import 'package:dart_macros/dart_macros.dart';
void main() {
// Initialize Flutter macros
FlutterMacros.initialize();
// Register macros for Flutter apps
FlutterMacros.registerFromAnnotations([
Define('APP_NAME', 'My Flutter App'),
Define('API_ENDPOINT', 'https://api.example.com'),
Define('DEBUG', true),
DefineMacro('FORMAT_CURRENCY', '"\$" + amount.toStringAsFixed(2)', parameters: ['amount']),
]);
// Configure platform-specific settings
FlutterMacros.configurePlatform(
platform: 'android',
debug: true,
additionalValues: {
'MIN_SDK_VERSION': 21,
'TARGET_SDK_VERSION': 33,
},
);
runApp(MyApp());
}
// Use macros just like on other platforms
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: Macros.get<String>('APP_NAME'),
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Macros.get<bool>('DEBUG') ? Brightness.light : Brightness.dark,
),
home: HomeScreen(),
);
}
}
Convert macro arguments to string literals:
@MacroFile()
@DefineMacro(
'STRINGIFY',
'"x"',
parameters: ['x'],
)
@DefineMacro(
'REPORT_VAR',
'"Variable " + "var" + " = " + var.toString()',
parameters: ['var'],
)
void main() async {
await initializeDartMacros();
// Usage
var name = MacroFunctions.STRINGIFY(user); // Evaluates to "user"
MacroFunctions.REPORT_VAR(count); // Prints: Variable count = 5
}
Join tokens together:
@MacroFile()
@DefineMacro(
'CONCAT',
'a + b',
parameters: ['a', 'b'],
)
void main() async {
await initializeDartMacros();
// Usage
var fullName = MacroFunctions.CONCAT("John", "Doe"); // Evaluates to "JohnDoe"
}
Special macros for debugging:
@MacroFile()
@Define('__DEBUG__', true)
@DefineMacro(
'DEBUG_PRINT',
'"Debug [" + __FILE__ + ":" + __LINE__ + "]: " + text',
parameters: ['text'],
)
void main() async {
await initializeDartMacros();
// Usage
MacroFunctions.DEBUG_PRINT("Starting initialization");
// Prints: Debug [example.dart:15]: Starting initialization
}
Built-in system macros:
void main() async {
await initializeDartMacros();
print(Macros.file); // Current source file name
print(Macros.line); // Current line number
print(Macros.date); // Compilation date
print(Macros.time); // Compilation time
}
Control compilation based on conditions:
@MacroFile()
@Define('DEBUG', true)
@Define('PLATFORM', 'android')
@Define('API_VERSION', 2)
class App {
void initialize() {
if (MacroFunctions.IFDEF('DEBUG')) {
print('Debug mode initialization');
}
if (MacroFunctions.IF_PLATFORM('android')) {
print('Initializing Android platform');
}
if (MacroFunctions.IF('DEBUG && API_VERSION >= 2')) {
print('Advanced debug features available');
}
}
}
dart_macros automatically selects the appropriate implementation based on platform capabilities:
// The same API works across all platforms
// On Flutter mobile, a non-reflection based implementation is used
// On Dart VM and web, a reflection-based implementation is used
final appName = Macros.get<String>('APP_NAME');
For Flutter applications, use the FlutterMacros
class to set up platform-specific configurations:
// Initialize with base settings
FlutterMacros.initialize();
// Configure for a specific platform
FlutterMacros.configurePlatform(
platform: defaultTargetPlatform.name.toLowerCase(),
debug: kDebugMode,
additionalValues: {
'DEVICE_TYPE': MediaQuery.of(context).size.width > 600 ? 'tablet' : 'phone',
'API_BASE_URL': _getApiUrl(),
'TIMEOUT_MS': 5000,
},
);
Register macros from annotations for mobile platforms:
// Register multiple macros at once
FlutterMacros.registerFromAnnotations([
Define('VERSION', '1.0.0'),
Define('MAX_ITEMS', 100),
Define('FEATURE_NEW_UI', false),
DefineMacro('SQUARE', 'x * x', parameters: ['x']),
]);
- π Document macro behavior and expansion
- π€ Use meaningful and clear macro names
- π§ Avoid side effects in macro arguments
- π§ͺ Test macro expansion in different contexts
- π Consider using
const
orstatic final
instead of simple object-like macros β οΈ Be careful with token concatenation and stringizing operators- π± For mobile apps, centralize macro registration in a configuration class
// Bad
@DefineMacro(
'SQUARE',
'x * x',
parameters: ['x'],
)
var result = MacroFunctions.SQUARE(i++); // i gets incremented twice
// Good
@DefineMacro(
'SQUARE',
'(x) * (x)',
parameters: ['x'],
)
// Bad
@DefineMacro(
'DOUBLE',
'x + x',
parameters: ['x'],
)
var result = 10 * MacroFunctions.DOUBLE(5); // Evaluates to 10 * 5 + 5 = 55
// Good
@DefineMacro(
'DOUBLE',
'(x) + (x)',
parameters: ['x'],
)
// Evaluates to 10 * (5 + 5) = 100
The main class for accessing macro values:
// Get a macro value
var debug = Macros.get<bool>('DEBUG');
// Access predefined macros
var currentFile = Macros.file;
var currentLine = Macros.line;
For invoking function-like macros:
// Using a function-like macro
var squared = MacroFunctions.SQUARE(5);
// Using predefined function-like macros
MacroFunctions.DEBUG_PRINT("Error occurred");
For Flutter-specific macro operations:
// Initialize for Flutter
FlutterMacros.initialize();
// Register macros for Flutter
FlutterMacros.registerFromAnnotations([...]);
// Configure platform-specific settings
FlutterMacros.configurePlatform(...);
// Mark a file for macro processing
@MacroFile()
// Define a simple constant macro
@Define('VERSION', '1.0.0')
// Define a function-like macro
@DefineMacro(
'MAX',
'a > b ? a : b',
parameters: ['a', 'b'],
)
// Platform-specific code
@Platform('android')
// Debug-only code
@Debug()
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.