Imported from tekartik/app_build.dart (
packages/firebase_build/skills/tekartik-firebase-build-hosting/SKILL.md). Install upstream withnpx skills add tekartik/app_build.dart --skill tekartik-firebase-build-hosting. Copyright stays with the author.
tekartik_firebase_build: Flutter web app on Firebase Hosting
package:tekartik_firebase_build/app_build.dart and firebase_deploy.dart
put a Flutter web build on Firebase Hosting: flutter build web (through
FlutterWebAppBuilder of tekartik_flutter_build, re-exported), copy of
build/web into the public/ folder of a hosting folder holding
firebase.json, then firebase deploy --only hosting:<target> or
firebase emulators:start from that folder. The firebase cli must be
installed and logged in. Dart VM only.
Guidelines
Setup
- Dependency (git, not on pub.dev), a dev dependency of the flutter app or
a dependency of its
*_toolspackage:
It depends ondev_dependencies: tekartik_firebase_build: git: url: https://github.com/tekartik/app_build.dart path: packages/firebase_buildtekartik_flutter_build(same repo,packages/flutter_build) and re-exports itsapp_build.dart, soFlutterWebAppBuildOptions,FlutterWebAppBuilder,FlutterWebAppOptionsandCommonAppBuilderExtcome withpackage:tekartik_firebase_build/app_build.dart. - Imports:
package:tekartik_firebase_build/app_build.dartforFlutterFirebaseWebAppBuilder,FlutterFirebaseWebAppOptions,flutterWebAppBuild,flutterWebAppBuildAndDeploy,flutterWebAppBuildAndServe;package:tekartik_firebase_build/firebase_deploy.dartforFirebaseDeployOptions,firebaseWebAppDeploy,firebaseWebAppServe,firebaseWebAppBuildToDeploy,firebaseDefaultDeployDir,FirebaseWebAppActionController. A build script imports both. - Tools:
firebasecli (npm install -g firebase-tools,firebase login) with access to the project,flutteron the PATH.
The hosting folder
- Default hosting folder:
deploy/firebase/hostinginside the app (FlutterFirebaseWebAppOptions.deployDirwhen null); the web build is copied to itspublic/subfolder (firebaseDefaultDeployDirisdeploy/firebase/hosting/public). Commitfirebase.jsonand.firebasercthere, git-ignorepublic/. firebase.jsonof that folder: ahostingentry with"public": "public","target": "<target>", the usualrewrites: [{"source": "**", "destination": "/index.html"}]and, for wasm builds, theCross-Origin-Embedder-Policy: credentialless/Cross-Origin-Opener-Policy: same-originheaders. A second hosting folder (deploy/firebase/hosting_wasm) with its ownfirebase.jsonis how you switch between hosting configurations: pass it asdeployDir.FirebaseDeployOptions(projectId:, hostingId:, target:):projectIdis the firebase project (--project),hostingIdthe hosting site id (the default site is the project id; extra sites have their own),targetthe hosting target name (dev,prod,beta)firebase.jsonrefers to. Before every deploy/serve the package reads.firebasercand, whentargets.<projectId>.hosting.<target>[0]is nothostingId, runsfirebase target:clearthenfirebase target:apply hosting <target> <hostingId>, which updates.firebaserc.copyWith(...)derives variants (one per flavor).build/web/deploy.yaml(from the appweb/deploy.yaml), when present, selects the files copied topublic/(files:/exclude:rules oftekartik_deploy); otherwise the wholebuild/webis copied.
The builder
FlutterFirebaseWebAppOptions({path, deployDir, deployOptions, buildOptions}):pathis the flutter app (default'.', made absolute),deployOptionsis required,buildOptionsis theFlutterWebAppBuildOptions(wasm:,target:) of the underlying flutter build,deployDirthe hosting folder (relative topathunless absolute).copyWith(...)on all four.FlutterFirebaseWebAppBuilder(options:):build()(flutter build + copy topublic/),copyBuildToDeploy()(copy only),deploy(),serve()(hosting emulator,firebase emulators:start --only hosting:<target>, blocks),buildAndDeploy(),buildAndServe(),clean()(flutter clean).deploy/servecopy the existing build topublic/again first, so onebuild()thendeploy()on several targets sharing the build works.targetisoptions.deployOptions.target;webAppBuilderis the innerFlutterWebAppBuilder(forrun(),reportJsSize(), the dhttpdserve());generateVersion(),generateVersionIfNeeded(),bumpVersion()apply (CommonAppBuilder).- Cancelling:
FirebaseWebAppActionController()passed ascontroller:tobuild,deploy,serve,buildAndDeploy,buildAndServe;controller.cancel()kills the runningfirebasecommand (the flutter build itself is not covered). One controller can serve several calls. - Function style, same behaviour without a builder object:
flutterWebAppBuild(dir)(plainflutter build web),firebaseWebAppBuildToDeploy(dir, deployDir:, folder:)(folderis thebuild/subfolder,'web'),firebaseWebAppDeploy(dir, options, deployDir:, controller:),firebaseWebAppServe(...),flutterWebAppBuildAndDeploy(dir, firebaseDeployOptions:, deployDir:),flutterWebAppBuildAndServe(...).firebaseWepAppBuildToDeploy(typo) is deprecated. - Menus:
tekartik_firebase_build_menu_flutterprovidesmenuFirebaseAppContent(builders:)for these builders; this package has no hosting menu itself (itsfirebase_project_menu.dartis about the firebase project folder, see thetekartik-firebase-build-projectskill). - Failures are
ShellExceptions fromprocess_run(firebaseexit code) orStateError('Missing build folder ...')when deploying before building.
Examples
tool/deploy_web.dart: one flavor per firebase project
// dart run tool/deploy_web.dart [dev|prod]
import 'package:tekartik_firebase_build/app_build.dart';
import 'package:tekartik_firebase_build/firebase_deploy.dart';
final devDeployOptions = FirebaseDeployOptions(
projectId: 'my-app-dev',
hostingId: 'my-app-dev',
target: 'dev',
);
final prodDeployOptions = devDeployOptions.copyWith(
projectId: 'my-app-prod',
hostingId: 'my-app-prod',
target: 'prod',
);
FlutterFirebaseWebAppBuilder builderFor(String flavor) {
return FlutterFirebaseWebAppBuilder(
options: FlutterFirebaseWebAppOptions(
deployOptions: flavor == 'prod' ? prodDeployOptions : devDeployOptions,
buildOptions: FlutterWebAppBuildOptions(
target: 'lib/main_$flavor.dart',
wasm: true,
),
// deploy/firebase/hosting/firebase.json; public/ receives the build
),
);
}
Future<void> main(List<String> args) async {
var flavor = args.isEmpty ? 'dev' : args.first;
await builderFor(flavor).buildAndDeploy();
}
Build once, deploy to two sites, cancel on Ctrl-C
import 'dart:io';
import 'package:tekartik_firebase_build/app_build.dart';
import 'package:tekartik_firebase_build/firebase_deploy.dart';
Future<void> main() async {
var options = FlutterFirebaseWebAppOptions(
path: '../my_app',
deployOptions: FirebaseDeployOptions(
projectId: 'my-app-prod',
hostingId: 'my-app-prod',
target: 'prod',
),
);
var prod = FlutterFirebaseWebAppBuilder(options: options);
var beta = FlutterFirebaseWebAppBuilder(
options: options.copyWith(
deployOptions: options.deployOptions.copyWith(
hostingId: 'my-app-beta',
target: 'beta',
),
),
);
var controller = FirebaseWebAppActionController();
var subscription = ProcessSignal.sigint.watch().listen((_) {
controller.cancel();
});
try {
await prod.build();
await beta.deploy(controller: controller); // copies the build again
await prod.deploy(controller: controller);
} finally {
await subscription.cancel();
}
}
Function style: wasm build served by the hosting emulator
import 'package:tekartik_firebase_build/app_build.dart';
import 'package:tekartik_firebase_build/firebase_deploy.dart';
Future<void> main() async {
var appDir = '.';
var hostingDir = 'deploy/firebase/hosting_wasm'; // sets COOP/COEP headers
var deployOptions = FirebaseDeployOptions(
projectId: 'my-app-dev',
hostingId: 'my-app-dev',
target: 'dev',
);
// flutterWebAppBuild(appDir) is a plain build; wasm needs the builder.
await FlutterWebAppBuilder(
options: FlutterWebAppOptions(
path: appDir,
buildOptions: FlutterWebAppBuildOptions(wasm: true),
),
).buildOnly();
await firebaseWebAppBuildToDeploy(appDir, deployDir: hostingDir);
await firebaseWebAppServe(appDir, deployOptions, deployDir: hostingDir);
}