Skip to content

feat: add Dart runtime delegate for emulator#9966

Open
Lyokone wants to merge 23 commits intofirebase:mainfrom
invertase:@invertase/dart
Open

feat: add Dart runtime delegate for emulator#9966
Lyokone wants to merge 23 commits intofirebase:mainfrom
invertase:@invertase/dart

Conversation

@Lyokone
Copy link

@Lyokone Lyokone commented Feb 24, 2026

Adds Dart as a new runtime for Firebase Functions, gated behind the functionsrunapionly experiment flag.

  • Dart runtime delegate: Detects Dart projects via pubspec.yaml, discovers functions from functions.yaml generated by
    build_runner, and manages build_runner watch for hot reload via the delegate's watch() method.
  • Emulator support: Dart uses a shared-process model (like Python) — one process serves all functions, routing by request path
    instead of FUNCTION_TARGET env var.
  • firebase init functions: Scaffolds a new Dart Functions project.
  • Added dart3 runtime with experimental status and isLanguageRuntime() helper for clean runtime checks.

Scenarios Tested

  • npm run build / npm run lint / npm test — all pass
  • npm run generate:json-schema — regenerated with dart3
  • Manual emulator testing: HTTPS triggers route correctly, hot reload works, clean shutdown

Sample Commands

firebase experiments:enable functionsrunapionly
firebase init functions  # Select "Dart"
firebase emulators:start --only functions

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Lyokone, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands Firebase Functions capabilities by introducing experimental support for Dart as a runtime. It provides a comprehensive Dart runtime delegate, integrates Dart's build_runner for function discovery and hot reload within the emulator, and adds firebase init scaffolding for new Dart projects. This enables developers to write and test Firebase Functions using Dart, leveraging its ecosystem and features.

Highlights

  • Dart Runtime Delegate: Introduced a new runtime delegate for Dart, enabling Firebase Functions to detect Dart projects via pubspec.yaml, discover functions from functions.yaml (generated by build_runner), and manage build_runner watch for hot reload.
  • Emulator Support for Dart: Implemented emulator support for Dart functions, utilizing a shared-process model similar to Python. This means one Dart process serves all functions, routing requests based on the path rather than FUNCTION_TARGET environment variables. Hot reload functionality is integrated through build_runner watch.
  • Firebase Init Functions for Dart: Added scaffolding for new Dart Functions projects within firebase init functions, including template files (pubspec.yaml, .gitignore, main.dart) and configuration for the latest supported Dart runtime and ignore rules.
  • Utility Function for Runtime Checks: Added a new helper function isLanguageRuntime() to simplify checks for whether an optional runtime string belongs to a specific language.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • scripts/agent-evals/templates/crashlytics-flutter/.dart_tool/package_config.json
    • Added a generated Dart package configuration file for a Crashlytics Flutter example.
  • scripts/agent-evals/templates/crashlytics-flutter/.dart_tool/package_graph.json
    • Added a generated Dart package dependency graph for a Crashlytics Flutter example.
  • scripts/agent-evals/templates/crashlytics-flutter/.dart_tool/version
    • Added a file specifying the Dart version for a Crashlytics Flutter example.
  • scripts/agent-evals/templates/crashlytics-flutter/.flutter-plugins-dependencies
    • Added a generated Flutter plugin dependencies file for a Crashlytics Flutter example.
  • scripts/agent-evals/templates/crashlytics-flutter/pubspec.lock
    • Added a Dart package lock file for a Crashlytics Flutter example.
  • src/deploy/functions/build.ts
    • Removed a comment indicating that the entryPoint field would become optional for the 'run' platform.
  • src/deploy/functions/runtimes/dart/index.ts
    • Added a new file implementing the Dart runtime delegate, including logic for pubspec.yaml detection, build_runner integration for function discovery and watch mode, and platform normalization.
  • src/deploy/functions/runtimes/index.ts
    • Reordered import statements for consistency.
  • src/deploy/functions/runtimes/supported/index.ts
    • Added isLanguageRuntime helper function to check if a runtime string corresponds to a given language.
  • src/emulator/functionsEmulator.ts
    • Imported isLanguageRuntime for runtime checks.
    • Added watchCleanups array to manage cleanup functions for watch processes.
    • Modified sendRequest to pass runtime information to worker pool methods and handle Dart-specific request paths.
    • Updated startWatching to first load triggers, then conditionally start build_runner watch for Dart projects, and manage its cleanup.
    • Removed redundant loadTriggers call from startWatching.
    • Added cleanup logic for watchCleanups in the stop method.
    • Implemented startDart method to initialize Dart runtime processes, including port assignment and environment setup.
    • Modified startRuntime to invoke startDart for Dart runtimes.
    • Updated addWorker to accept and pass the runtime type.
    • Adjusted handleHttpsRequest to construct Dart-specific request paths for shared-process routing and pass runtime information.
  • src/emulator/functionsRuntimeWorker.ts
    • Imported isLanguageRuntime for runtime checks.
    • Modified getKey to return a shared key (~dart-shared~) for Dart runtimes, reflecting its shared-process model.
    • Updated readyForWork, getIdleWorker, submitRequest, addWorker, getTriggerWorkers, and setTriggerWorkers to accept and utilize the runtime parameter for Dart-specific worker management.
  • src/init/features/functions/dart.ts
    • Added a new file providing setup logic for Dart functions during firebase init, including template generation for pubspec.yaml, .gitignore, and main.dart, and an option to install dependencies.
  • src/init/features/functions/index.ts
    • Added 'Dart' as a selectable language option in the firebase init functions wizard.
    • Configured Dart-specific ignore patterns and set the default runtime for Dart projects during initialization.
Activity
  • The author has performed manual emulator testing, confirming correct routing for HTTPS triggers, functional hot reload, and clean shutdown.
  • The author has verified that npm run build, npm run lint, and npm test all pass.
  • The author has regenerated the JSON schema using npm run generate:json-schema with dart3.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request introduces Dart runtime support for Firebase Functions, including a new runtime delegate, emulator support with hot reload via build_runner watch, and scaffolding for new Dart projects during firebase init functions. The changes involve adding new files for Dart-specific configurations and modifying existing TypeScript files to integrate Dart into the functions emulator and runtime detection logic. The code appears to be well-structured and follows existing patterns for other runtimes like Python and Node.js. The changes also include updates to the .gitignore template and the firebase.json configuration to properly handle Dart projects.

import { latest } from "../../../deploy/functions/runtimes/supported";

// TODO(ehesp): Create these template files in templates/init/functions/dart/
// TODO(ehesp): Dont use relative path for firebase_functions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The TODO comment regarding firebase_functions relative path should be addressed. Using a relative path for a dependency can lead to issues in different environments or when the project structure changes. It's generally better to use a published package or a more robust dependency management approach.

// TODO(ehesp): Create these template files in templates/init/functions/dart/
// TODO(ehesp): Use a published package for firebase_functions or a more robust dependency management approach.


dependencies:
firebase_functions:
path: ../
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using a relative path for firebase_functions in pubspec.yaml can be problematic for deployment and dependency resolution. It's generally recommended to use a published package from pub.dev or a git dependency with a specific ref for better stability and maintainability.

  firebase_functions: ^x.y.z # Replace with the actual version from pub.dev
    # path: ../ # Remove or comment out this line if using a published package


// Necessary for the GCF API to determine what code to load with the Functions Framework.
// Will become optional once "run" is supported as a platform
entryPoint: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This comment is no longer accurate as entryPoint is now required for all platforms, including run (which is normalized to gcfv2 for emulator compatibility). It can be removed.

Comment on lines 115 to 119
if (code !== 0 && code !== null) {
logger.debug(`build_runner exited with code ${code}. Hot reload may not work.`);
if (!initialBuildComplete) {
rejectInitialBuild(new Error(`build_runner exited with code ${code}`));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error message for build_runner exiting with a non-zero code should be more informative. It currently suggests that hot reload 'may not work', but if the initial build fails, hot reload definitely won't work, and the user should be informed that the functions might not be deployed or emulated correctly.

        logger.debug(`build_runner exited with code ${code}. Initial build failed.`);
        if (!initialBuildComplete) {
          rejectInitialBuild(new FirebaseError(`build_runner exited with code ${code}. Your Dart functions may not be deployed or emulated correctly.`));
        }

Comment on lines 125 to 128
logger.debug(`Failed to start build_runner: ${err.message}`);
if (!initialBuildComplete) {
rejectInitialBuild(err);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error message for build_runner failing to start should be more specific about the impact on the functions, similar to the previous comment. If build_runner cannot start, the functions will not be built or emulated.

      logger.debug(`Failed to start build_runner: ${err.message}. Your Dart functions may not be deployed or emulated correctly.`);
      if (!initialBuildComplete) {
        rejectInitialBuild(err);
      }

const record = this.getTriggerRecordByKey(this.getTriggerKey(trigger));
const pool = this.workerPools[record.backend.codebase];
if (!pool.readyForWork(trigger.id)) {
const runtime = record.backend.runtime;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The runtime variable is declared but not used in this block. It should be removed to avoid unnecessary declarations.

    if (!pool.readyForWork(trigger.id, record.backend.runtime)) {

}
}
const worker = pool.getIdleWorker(trigger.id)!;
const worker = pool.getIdleWorker(trigger.id, runtime)!;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The runtime variable is declared but not used in this block. It should be removed to avoid unnecessary declarations.

    const worker = pool.getIdleWorker(trigger.id, record.backend.runtime)!;


const pool = this.workerPools[record.backend.codebase];
if (!pool.readyForWork(trigger.id)) {
const runtime = record.backend.runtime;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The runtime variable is declared but not used in this block. It should be removed to avoid unnecessary declarations.

    if (!pool.readyForWork(trigger.id, record.backend.runtime)) {

Comment on lines +306 to +308
if (isLanguageRuntime(runtime, "dart")) {
return "~dart-shared~";
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The comment states that Dart loads all functions and routes based on request path, which is true. However, the current implementation uses ~dart-shared~ as the key, which means all Dart functions in a codebase will share the same worker. This is consistent with the Python runtime, but the comment could be more explicit about this shared process model for Dart.

    // For Dart, use a shared key so all functions in a codebase share the same process.
    // Dart loads all functions and routes based on request path, similar to Python.
    if (isLanguageRuntime(runtime, "dart")) {

[
"compile",
"exe",
"lib/main.dart",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure we want to keep this entry point hard coded like this for "lib/main.dart"?

return;
}

// Requires Dart 3.8+ for --target-os and --target-arch support.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it's worthwhile to enforce this "Requires Dart 3.8+" or check to provide a error message if the user's local Dart SDK is too old?

{ exit: 1 },
);
}
return Promise.resolve(new Delegate(context.projectId, context.sourceDir, runtime));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it makes sense to add a check like in validate() that explicitly verifies the binary exists and optionally checks the version (going back to the other comment I left about requiring Dart 3.8+)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants