Skip to content

Commit 74688ce

Browse files
Have flutter.js load local canvaskit instead of the CDN when appropriate (#150806)
If the user specifies the `--no-web-resources-cdn` or `--local-web-sdk`, we should use the local version of CanvasKit. `flutter.js` now has a flag that can be specified in the build configuration that tells it to load locally instead. Also, added a link to the relevant docs in the web template warnings. This addresses flutter/flutter#148713 Also fixes flutter/flutter#145559
1 parent ab4d422 commit 74688ce

File tree

14 files changed

+213
-62
lines changed

14 files changed

+213
-62
lines changed

packages/flutter_tools/lib/src/build_info.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class BuildInfo {
4646
this.initializeFromDill,
4747
this.assumeInitializeFromDillUpToDate = false,
4848
this.buildNativeAssets = true,
49+
this.useLocalCanvasKit = false,
4950
}) : extraFrontEndOptions = extraFrontEndOptions ?? const <String>[],
5051
extraGenSnapshotOptions = extraGenSnapshotOptions ?? const <String>[],
5152
fileSystemRoots = fileSystemRoots ?? const <String>[],
@@ -180,6 +181,9 @@ class BuildInfo {
180181
/// If set, builds native assets with `build.dart` from all packages.
181182
final bool buildNativeAssets;
182183

184+
/// If set, web builds will use the locally built CanvasKit instead of using the CDN
185+
final bool useLocalCanvasKit;
186+
183187
static const BuildInfo debug = BuildInfo(BuildMode.debug, null, trackWidgetCreation: true, treeShakeIcons: false);
184188
static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
185189
static const BuildInfo jitRelease = BuildInfo(BuildMode.jitRelease, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
@@ -260,6 +264,8 @@ class BuildInfo {
260264
kBuildName: buildName!,
261265
if (buildNumber != null)
262266
kBuildNumber: buildNumber!,
267+
if (useLocalCanvasKit)
268+
kUseLocalCanvasKitFlag: useLocalCanvasKit.toString(),
263269
};
264270
}
265271

@@ -941,6 +947,9 @@ const String kCodesignIdentity = 'CodesignIdentity';
941947
/// only the glyphs used by the application.
942948
const String kIconTreeShakerFlag = 'TreeShakeIcons';
943949

950+
/// Controls whether a web build should use local canvaskit or the CDN
951+
const String kUseLocalCanvasKitFlag = 'UseLocalCanvasKit';
952+
944953
/// The input key for an SkSL bundle path.
945954
const String kBundleSkSLPath = 'BundleSkSLPath';
946955

packages/flutter_tools/lib/src/build_system/targets/web.dart

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,19 @@ abstract class Dart2WebTarget extends Target {
104104
Iterable<File> buildFiles(Environment environment);
105105
Iterable<String> get buildPatternStems;
106106

107+
List<String> computeDartDefines(Environment environment) {
108+
final List<String> dartDefines = compilerConfig.renderer.updateDartDefines(
109+
decodeDartDefines(environment.defines, kDartDefines),
110+
);
111+
if (environment.defines[kUseLocalCanvasKitFlag] != 'true') {
112+
final bool canvasKitUrlAlreadySet = dartDefines.any((String define) => define.startsWith('FLUTTER_WEB_CANVASKIT_URL='));
113+
if (!canvasKitUrlAlreadySet) {
114+
dartDefines.add('FLUTTER_WEB_CANVASKIT_URL=https://www.gstatic.com/flutter-canvaskit/${globals.flutterVersion.engineRevision}/');
115+
}
116+
}
117+
return dartDefines;
118+
}
119+
107120
@override
108121
List<Target> get dependencies => const <Target>[
109122
WebEntrypointTarget(),
@@ -155,9 +168,6 @@ class Dart2JSTarget extends Dart2WebTarget {
155168
final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
156169
final Artifacts artifacts = environment.artifacts;
157170
final String platformBinariesPath = artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder).path;
158-
final List<String> dartDefines = compilerConfig.renderer.updateDartDefines(
159-
decodeDartDefines(environment.defines, kDartDefines),
160-
);
161171
final List<String> sharedCommandOptions = <String>[
162172
artifacts.getArtifactPath(Artifact.engineDartBinary, platform: TargetPlatform.web_javascript),
163173
'--disable-dart-dev',
@@ -169,7 +179,7 @@ class Dart2JSTarget extends Dart2WebTarget {
169179
'-Ddart.vm.profile=true'
170180
else
171181
'-Ddart.vm.product=true',
172-
for (final String dartDefine in dartDefines)
182+
for (final String dartDefine in computeDartDefines(environment))
173183
'-D$dartDefine',
174184
];
175185

@@ -287,9 +297,6 @@ class Dart2WasmTarget extends Dart2WebTarget {
287297
final File depFile = environment.buildDir.childFile('dart2wasm.d');
288298
final String platformBinariesPath = artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder).path;
289299
final String platformFilePath = environment.fileSystem.path.join(platformBinariesPath, 'dart2wasm_platform.dill');
290-
final List<String> dartDefines = compilerConfig.renderer.updateDartDefines(
291-
decodeDartDefines(environment.defines, kDartDefines),
292-
);
293300

294301
assert(buildMode == BuildMode.release || buildMode == BuildMode.profile);
295302
final List<String> compilationArgs = <String>[
@@ -309,7 +316,7 @@ class Dart2WasmTarget extends Dart2WebTarget {
309316
else
310317
'-Ddart.vm.product=true',
311318
...decodeCommaSeparated(environment.defines, kExtraFrontEndOptions),
312-
for (final String dartDefine in dartDefines)
319+
for (final String dartDefine in computeDartDefines(environment))
313320
'-D$dartDefine',
314321
'--extra-compiler-option=--depfile=${depFile.path}',
315322

@@ -380,23 +387,9 @@ class WebReleaseBundle extends Target {
380387

381388
WebReleaseBundle._({
382389
required this.compileTargets,
383-
}) : templatedFilesTarget = WebTemplatedFiles(generateBuildConfigString(compileTargets));
384-
385-
static String generateBuildConfigString(List<Dart2WebTarget> compileTargets) {
386-
final List<Map<String, Object?>> buildDescriptions = compileTargets.map(
387-
(Dart2WebTarget target) => target.buildConfig
388-
).toList();
389-
final Map<String, Object?> buildConfig = <String, Object?>{
390-
'engineRevision': globals.flutterVersion.engineRevision,
391-
'builds': buildDescriptions,
392-
};
393-
return '''
394-
if (!window._flutter) {
395-
window._flutter = {};
396-
}
397-
_flutter.buildConfig = ${jsonEncode(buildConfig)};
398-
''';
399-
}
390+
}) : templatedFilesTarget = WebTemplatedFiles(
391+
compileTargets.map((Dart2WebTarget target) => target.buildConfig).toList()
392+
);
400393

401394
final List<Dart2WebTarget> compileTargets;
402395
final WebTemplatedFiles templatedFilesTarget;
@@ -516,12 +509,12 @@ _flutter.buildConfig = ${jsonEncode(buildConfig)};
516509
}
517510

518511
class WebTemplatedFiles extends Target {
519-
WebTemplatedFiles(this.buildConfigString);
512+
WebTemplatedFiles(this.buildDescriptions);
520513

521-
final String buildConfigString;
514+
final List<Map<String, Object?>> buildDescriptions;
522515

523516
@override
524-
String get buildKey => buildConfigString;
517+
String get buildKey => jsonEncode(buildDescriptions);
525518

526519
void _emitWebTemplateWarning(
527520
Environment environment,
@@ -533,6 +526,21 @@ class WebTemplatedFiles extends Target {
533526
);
534527
}
535528

529+
String buildConfigString(Environment environment) {
530+
final Map<String, Object> buildConfig = <String, Object>{
531+
'engineRevision': globals.flutterVersion.engineRevision,
532+
'builds': buildDescriptions,
533+
if (environment.defines[kUseLocalCanvasKitFlag] == 'true')
534+
'useLocalCanvasKit': true,
535+
};
536+
return '''
537+
if (!window._flutter) {
538+
window._flutter = {};
539+
}
540+
_flutter.buildConfig = ${jsonEncode(buildConfig)};
541+
''';
542+
}
543+
536544
@override
537545
Future<void> build(Environment environment) async {
538546
final Directory webResources = environment.projectDir
@@ -555,6 +563,8 @@ class WebTemplatedFiles extends Target {
555563
'flutter.js',
556564
));
557565

566+
final String buildConfig = buildConfigString(environment);
567+
558568
// Insert a random hash into the requests for service_worker.js. This is not a content hash,
559569
// because it would need to be the hash for the entire bundle and not just the resource
560570
// in question.
@@ -563,7 +573,7 @@ class WebTemplatedFiles extends Target {
563573
baseHref: '',
564574
serviceWorkerVersion: serviceWorkerVersion,
565575
flutterJsFile: flutterJsFile,
566-
buildConfig: buildConfigString,
576+
buildConfig: buildConfig,
567577
);
568578

569579
final File outputFlutterBootstrapJs = fileSystem.file(fileSystem.path.join(
@@ -585,7 +595,7 @@ class WebTemplatedFiles extends Target {
585595
baseHref: environment.defines[kBaseHref] ?? '/',
586596
serviceWorkerVersion: serviceWorkerVersion,
587597
flutterJsFile: flutterJsFile,
588-
buildConfig: buildConfigString,
598+
buildConfig: buildConfig,
589599
flutterBootstrapJs: bootstrapTemplate.content,
590600
);
591601
final File outputIndexHtml = fileSystem.file(fileSystem.path.join(

packages/flutter_tools/lib/src/commands/run.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,19 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
235235

236236
bool get useWasm => boolArg(FlutterOptions.kWebWasmFlag);
237237

238+
bool get useLocalCanvasKit {
239+
// If we have specified not to use CDN, use local CanvasKit
240+
if (!boolArg(FlutterOptions.kWebResourcesCdnFlag)) {
241+
return true;
242+
}
243+
244+
// If we are using a locally built web sdk, we should use local CanvasKit
245+
if (stringArg(FlutterGlobalOptions.kLocalWebSDKOption, global: true) != null) {
246+
return true;
247+
}
248+
return false;
249+
}
250+
238251
WebRendererMode get webRenderer => WebRendererMode.fromCliOption(
239252
stringArg(FlutterOptions.kWebRendererFlag),
240253
useWasm: useWasm
@@ -274,6 +287,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
274287
webHeaders: webHeaders,
275288
webRenderer: webRenderer,
276289
webUseWasm: useWasm,
290+
webUseLocalCanvaskit: useLocalCanvasKit,
277291
enableImpeller: enableImpeller,
278292
enableVulkanValidation: enableVulkanValidation,
279293
uninstallFirst: uninstallFirst,
@@ -325,6 +339,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
325339
webHeaders: webHeaders,
326340
webRenderer: webRenderer,
327341
webUseWasm: useWasm,
342+
webUseLocalCanvaskit: useLocalCanvasKit,
328343
vmserviceOutFile: stringArg('vmservice-out-file'),
329344
fastStart: argParser.options.containsKey('fast-start')
330345
&& boolArg('fast-start')

packages/flutter_tools/lib/src/commands/test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
412412
debugLogsDirectoryPath: debugLogsDirectoryPath,
413413
webRenderer: webRenderer,
414414
webUseWasm: useWasm,
415+
webUseLocalCanvaskit: true,
415416
);
416417

417418
String? testAssetDirectory;

packages/flutter_tools/lib/src/device.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,7 @@ class DebuggingOptions {
994994
this.webLaunchUrl,
995995
WebRendererMode? webRenderer,
996996
this.webUseWasm = false,
997+
this.webUseLocalCanvaskit = false,
997998
this.vmserviceOutFile,
998999
this.fastStart = false,
9991000
this.nullAssertions = false,
@@ -1026,6 +1027,7 @@ class DebuggingOptions {
10261027
this.webHeaders = const <String, String>{},
10271028
WebRendererMode? webRenderer,
10281029
this.webUseWasm = false,
1030+
this.webUseLocalCanvaskit = false,
10291031
this.cacheSkSL = false,
10301032
this.traceAllowlist,
10311033
this.enableImpeller = ImpellerStatus.platformDefault,
@@ -1108,6 +1110,7 @@ class DebuggingOptions {
11081110
required this.webLaunchUrl,
11091111
required this.webRenderer,
11101112
required this.webUseWasm,
1113+
required this.webUseLocalCanvaskit,
11111114
required this.vmserviceOutFile,
11121115
required this.fastStart,
11131116
required this.nullAssertions,
@@ -1198,6 +1201,9 @@ class DebuggingOptions {
11981201
/// Whether to compile to webassembly
11991202
final bool webUseWasm;
12001203

1204+
/// If true, serve CanvasKit assets locally rather than using the CDN.
1205+
final bool webUseLocalCanvaskit;
1206+
12011207
/// A file where the VM Service URL should be written after the application is started.
12021208
final String? vmserviceOutFile;
12031209
final bool fastStart;
@@ -1308,6 +1314,7 @@ class DebuggingOptions {
13081314
'webHeaders': webHeaders,
13091315
'webRenderer': webRenderer.name,
13101316
'webUseWasm': webUseWasm,
1317+
'webUseLocalCanvaskit': webUseLocalCanvaskit,
13111318
'vmserviceOutFile': vmserviceOutFile,
13121319
'fastStart': fastStart,
13131320
'nullAssertions': nullAssertions,
@@ -1365,6 +1372,7 @@ class DebuggingOptions {
13651372
webLaunchUrl: json['webLaunchUrl'] as String?,
13661373
webRenderer: WebRendererMode.values.byName(json['webRenderer']! as String),
13671374
webUseWasm: json['webUseWasm']! as bool,
1375+
webUseLocalCanvaskit: json['webUseLocalCanvaskit']! as bool,
13681376
vmserviceOutFile: json['vmserviceOutFile'] as String?,
13691377
fastStart: json['fastStart']! as bool,
13701378
nullAssertions: json['nullAssertions']! as bool,

packages/flutter_tools/lib/src/drive/web_driver_service.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ class WebDriverService extends DriverService {
7979
port: debuggingOptions.port,
8080
hostname: debuggingOptions.hostname,
8181
webRenderer: debuggingOptions.webRenderer,
82-
webUseWasm: debuggingOptions.webUseWasm
82+
webUseWasm: debuggingOptions.webUseWasm,
83+
webUseLocalCanvaskit: debuggingOptions.webUseLocalCanvaskit,
8384
)
8485
: DebuggingOptions.enabled(
8586
buildInfo,
@@ -88,6 +89,7 @@ class WebDriverService extends DriverService {
8889
disablePortPublication: debuggingOptions.disablePortPublication,
8990
webRenderer: debuggingOptions.webRenderer,
9091
webUseWasm: debuggingOptions.webUseWasm,
92+
webUseLocalCanvaskit: debuggingOptions.webUseLocalCanvaskit,
9193
),
9294
stayResident: true,
9395
flutterProject: FlutterProject.current(),

packages/flutter_tools/lib/src/isolated/devfs_web.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ class WebAssetServer implements AssetReader {
120120
this._nullSafetyMode,
121121
this._ddcModuleSystem, {
122122
required this.webRenderer,
123+
required this.useLocalCanvasKit,
123124
}) : basePath = _getWebTemplate('index.html', _kDefaultIndex).getBaseHref();
124125

125126
// Fallback to "application/octet-stream" on null which
@@ -181,6 +182,7 @@ class WebAssetServer implements AssetReader {
181182
NullSafetyMode nullSafetyMode, {
182183
required WebRendererMode webRenderer,
183184
required bool isWasm,
185+
required bool useLocalCanvasKit,
184186
bool testMode = false,
185187
DwdsLauncher dwdsLauncher = Dwds.start,
186188
// TODO(markzipan): Make sure this default value aligns with that in the debugger options.
@@ -233,6 +235,7 @@ class WebAssetServer implements AssetReader {
233235
nullSafetyMode,
234236
ddcModuleSystem,
235237
webRenderer: webRenderer,
238+
useLocalCanvasKit: useLocalCanvasKit,
236239
);
237240
if (testMode) {
238241
return server;
@@ -530,6 +533,8 @@ class WebAssetServer implements AssetReader {
530533
/// Determines what rendering backed to use.
531534
final WebRendererMode webRenderer;
532535

536+
final bool useLocalCanvasKit;
537+
533538
String get _buildConfigString {
534539
final Map<String, dynamic> buildConfig = <String, dynamic>{
535540
'engineRevision': globals.flutterVersion.engineRevision,
@@ -540,6 +545,7 @@ class WebAssetServer implements AssetReader {
540545
'mainJsPath': 'main.dart.js',
541546
},
542547
],
548+
if (useLocalCanvasKit) 'useLocalCanvasKit' : true,
543549
};
544550
return '''
545551
if (!window._flutter) {
@@ -740,6 +746,7 @@ class WebDevFS implements DevFS {
740746
required this.ddcModuleSystem,
741747
required this.webRenderer,
742748
required this.isWasm,
749+
required this.useLocalCanvasKit,
743750
required this.rootDirectory,
744751
this.testMode = false,
745752
}) : _port = port;
@@ -767,6 +774,7 @@ class WebDevFS implements DevFS {
767774
final String? tlsCertKeyPath;
768775
final WebRendererMode webRenderer;
769776
final bool isWasm;
777+
final bool useLocalCanvasKit;
770778

771779
late WebAssetServer webAssetServer;
772780

@@ -868,6 +876,7 @@ class WebDevFS implements DevFS {
868876
nullSafetyMode,
869877
webRenderer: webRenderer,
870878
isWasm: isWasm,
879+
useLocalCanvasKit: useLocalCanvasKit,
871880
testMode: testMode,
872881
ddcModuleSystem: ddcModuleSystem,
873882
);

packages/flutter_tools/lib/src/isolated/resident_web_runner.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
318318
ddcModuleSystem: debuggingOptions.buildInfo.ddcModuleFormat == DdcModuleFormat.ddc,
319319
webRenderer: debuggingOptions.webRenderer,
320320
isWasm: debuggingOptions.webUseWasm,
321+
useLocalCanvasKit: debuggingOptions.webUseLocalCanvaskit,
321322
rootDirectory: fileSystem.directory(projectRootPath),
322323
);
323324
Uri url = await device!.devFS!.create();

0 commit comments

Comments
 (0)