惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
云风的 BLOG
云风的 BLOG
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
D
Docker
H
Help Net Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Azure Blog
Microsoft Azure Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
月光博客
月光博客
C
Check Point Blog
S
SegmentFault 最新的问题
T
The Blog of Author Tim Ferriss
J
Java Code Geeks
M
MIT News - Artificial intelligence
B
Blog RSS Feed
MyScale Blog
MyScale Blog
大猫的无限游戏
大猫的无限游戏
Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
美团技术团队
I
InfoQ
Blog — PlanetScale
Blog — PlanetScale

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Dart Macros — Compile-time Code Generation Without Build ...
kanta13jp1 · 2026-04-29 · via DEV Community

kanta13jp1

Dart Macros — Compile-time Code Generation Without Build Runner

Dart 3.4 (Flutter 3.22) introduced Dart Macros as an experimental feature: compile-time code generation that works without build_runner. A single annotation can auto-generate fromJson/toJson, copyWith, or Equatable-style equality — all processed directly by the compiler.

What Are Dart Macros? Key Differences from build_runner

Traditional build_runner-based code generation has two pain points:

  1. Slow build steps: You must manually run dart run build_runner build, which can take seconds to minutes on large projects
  2. Generated file clutter: The .g.dart files need to be committed (or .gitignored), causing team disagreements

Dart Macros are processed by the compiler itself, eliminating the separate build step. IDE autocomplete works immediately, and there are no .g.dart files written to disk.

build_runner Dart Macros
Build execution Manual or watch mode Not needed (compiler-integrated)
Generated files .g.dart files on disk No generated files
IDE completion Available after build Immediate
Maturity Stable (Production ready) Experimental (as of Dart 3.4)

Auto-generating fromJson/toJson with @JsonCodable

@JsonCodable is the first official Dart macro. Add it to a class and JSON serialization is generated automatically.

// pubspec.yaml additions:
// dependencies:
//   json: ^0.20.0   # macros-based JSON package
// dart:
//   experiments:
//     - macros

import 'package:json/json.dart';

@JsonCodable()
class UserProfile {
  final String id;
  final String displayName;
  final String? avatarUrl;
  final DateTime createdAt;
}

// The above automatically makes available:
// UserProfile.fromJson(Map<String, dynamic> json)
// Map<String, dynamic> toJson()

// Usage
final profile = UserProfile.fromJson({
  'id': 'uuid-123',
  'display_name': 'Kanta',
  'avatar_url': null,
  'created_at': '2026-04-29T12:00:00Z',
});
print(profile.toJson());
// {id: uuid-123, displayName: Kanta, ...}

Enter fullscreen mode Exit fullscreen mode

Previously, achieving the same with json_serializable required @JsonSerializable(), running build_runner, and committing the .g.dart output. Macros eliminate all of that.

Writing a Custom Macro with ClassDeclarationMacro

To create your own macro, implement the ClassDeclarationsMacro interface using the macro keyword.

// lib/macros/copy_with.dart
import 'dart:async';
import 'package:macros/macros.dart';

macro class CopyWith implements ClassDeclarationsMacro {
  const CopyWith();

  @override
  Future<void> buildDeclarationsForClass(
    ClassDeclaration clazz,
    MemberDeclarationBuilder builder,
  ) async {
    // Introspect the class fields at compile time
    final fields = await builder.fieldsOf(clazz);

    final params = fields.map((f) {
      final typeName = f.type.code.debugString;
      return '${typeName}? ${f.identifier.name}';
    }).join(', ');

    final assignments = fields.map((f) {
      final name = f.identifier.name;
      return '$name: $name ?? this.$name';
    }).join(', ');

    // Inject the copyWith method into the class
    builder.declareInClass(DeclarationCode.fromString('''
      ${clazz.identifier.name} copyWith({$params}) {
        return ${clazz.identifier.name}($assignments);
      }
    '''));
  }
}

Enter fullscreen mode Exit fullscreen mode

// Using the custom macro
@CopyWith()
class Post {
  final String id;
  final String title;
  final String body;
  const Post({required this.id, required this.title, required this.body});
}

// copyWith is now available without any generated file
final updated = post.copyWith(title: 'New Title');

Enter fullscreen mode Exit fullscreen mode

Flutter Use Cases — Equatable Replacement

Value equality (needed for widget rebuild optimization and unit tests) is a natural target for macros.

// ValueEquality macro — replaces Equatable package
macro class ValueEquality implements ClassDeclarationsMacro {
  const ValueEquality();

  @override
  Future<void> buildDeclarationsForClass(
    ClassDeclaration clazz,
    MemberDeclarationBuilder builder,
  ) async {
    final fields = await builder.fieldsOf(clazz);
    final fieldList = fields.map((f) => f.identifier.name).join(', ');

    builder.declareInClass(DeclarationCode.fromString('''
      @override
      bool operator ==(Object other) =>
          identical(this, other) ||
          other is ${clazz.identifier.name} &&
          runtimeType == other.runtimeType &&
          ${fields.map((f) => '${f.identifier.name} == other.${f.identifier.name}').join(' && ')};

      @override
      int get hashCode => Object.hash($fieldList);
    '''));
  }
}

// Stack macros freely
@ValueEquality()
@CopyWith()
class FilterState {
  final String query;
  final List<String> tags;
  final bool showCompleted;
  const FilterState({
    required this.query,
    required this.tags,
    required this.showCompleted,
  });
}

// Both == and copyWith work without build_runner
final next = state.copyWith(query: 'flutter');
print(state == next); // false

Enter fullscreen mode Exit fullscreen mode

Current Limitations and Caveats

Dart Macros are experimental as of Dart 3.4. Be aware of these constraints before adopting them in production:

  1. Requires experiment flag: Must enable macros in pubspec.yaml under dart: experiments:
  2. Limited pub.dev ecosystem: Few packages use macros yet; the ecosystem is still maturing
  3. Harder to debug: Macro errors can be more cryptic than build_runner errors
  4. Platform coverage not guaranteed: Test on Web, iOS, and Android separately
  5. API is not stable: The macro API may change in future Dart versions
# pubspec.yaml: enabling the experiment
environment:
  sdk: '>=3.4.0 <4.0.0'

dart:
  experiments:
    - macros

Enter fullscreen mode Exit fullscreen mode

Recommended approach for production apps: Use @JsonCodable for new data models if you're already on Dart 3.4+ and comfortable with experimental features. For copyWith and Equatable, stick with freezed + build_runner until macros stabilize — the freezed ecosystem is battle-tested and offers additional features like sealed classes.

Summary

Dart Macros are the future replacement for build_runner, but today they remain experimental. @JsonCodable is worth trying on greenfield projects. Custom macros using ClassDeclarationsMacro are powerful for eliminating boilerplate, but the API will likely evolve before Dart 4.0 stabilizes them. Learn the concepts now, adopt carefully in production.