Unverified Commit c55b3220 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] fix performance of tree-shake-icons (#55417)

Fixes the performance issue with tree-shake-icons and filters to ttf mime type. Does not change default or error behavior.
parent 72397fd4
......@@ -60,9 +60,9 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory) a
file.parent.createSync(recursive: true);
final DevFSContent content = entry.value;
if (content is DevFSFileContent && content.file is File) {
inputs.add(globals.fs.file(content.file.path));
inputs.add(content.file as File);
if (!await iconTreeShaker.subsetFont(
inputPath: content.file.path,
input: content.file as File,
outputPath: file.path,
relativePath: entry.key,
)) {
......
......@@ -4,6 +4,7 @@
import 'package:meta/meta.dart';
import 'package:process/process.dart';
import 'package:mime/mime.dart' as mime;
import '../../artifacts.dart';
import '../../base/common.dart';
......@@ -66,6 +67,12 @@ class IconTreeShaker {
}
}
/// The MIME type for ttf fonts.
static const Set<String> kTtfMimeTypes = <String>{
'font/ttf', // based on internet search
'application/x-font-ttf', // based on running locally.
};
/// The [Source] inputs that targets using this should depend on.
///
/// See [Target.inputs].
......@@ -77,6 +84,7 @@ class IconTreeShaker {
final Environment _environment;
final String _fontManifest;
Future<void> _iconDataProcessing;
Map<String, _IconTreeShakerData> _iconData;
final ProcessManager _processManager;
......@@ -89,10 +97,10 @@ class IconTreeShaker {
&& _environment.defines[kIconTreeShakerFlag] == 'true'
&& _environment.defines[kBuildMode] != 'debug';
/// Fills the [_iconData] map.
Future<Map<String, _IconTreeShakerData>> _getIconData(Environment environment) async {
// Fills the [_iconData] map.
Future<void> _getIconData(Environment environment) async {
if (!enabled) {
return null;
return;
}
final File appDill = environment.buildDir.childFile('app.dill');
......@@ -119,12 +127,14 @@ class IconTreeShaker {
);
if (fonts.length != iconData.length) {
throwToolExit('Expected to find fonts for ${iconData.keys}, but found '
'${fonts.keys}. This usually means you are refering to '
'font families in an IconData class but not including them '
'in the assets section of your pubspec.yaml, are missing '
'the package that would include them, or are missing '
'"uses-material-design: true".');
throwToolExit(
'Expected to find fonts for ${iconData.keys}, but found '
'${fonts.keys}. This usually means you are refering to '
'font families in an IconData class but not including them '
'in the assets section of your pubspec.yaml, are missing '
'the package that would include them, or are missing '
'"uses-material-design: true".',
);
}
final Map<String, _IconTreeShakerData> result = <String, _IconTreeShakerData>{};
......@@ -135,13 +145,11 @@ class IconTreeShaker {
codePoints: iconData[entry.key],
);
}
return result;
_iconData = result;
}
/// Calls font-subset, which transforms the `inputPath` font file to a
/// subsetted version at `outputPath`.
///
/// The `relativePath` parameter
/// Calls font-subset, which transforms the [input] font file to a
/// subsetted version at [outputPath].
///
/// All parameters are required.
///
......@@ -150,15 +158,24 @@ class IconTreeShaker {
/// If the font-subset subprocess fails, it will [throwToolExit].
/// Otherwise, it will return true.
Future<bool> subsetFont({
@required String inputPath,
@required File input,
@required String outputPath,
@required String relativePath,
}) async {
if (!enabled) {
return false;
}
_iconData ??= await _getIconData(_environment);
if (input.lengthSync() < 12) {
return false;
}
final String mimeType = mime.lookupMimeType(
input.path,
headerBytes: await input.openRead(0, 12).first,
);
if (!kTtfMimeTypes.contains(mimeType)) {
return false;
}
await (_iconDataProcessing ??= _getIconData(_environment));
assert(_iconData != null);
final _IconTreeShakerData iconTreeShakerData = _iconData[relativePath];
......@@ -176,7 +193,7 @@ class IconTreeShaker {
final List<String> cmd = <String>[
fontSubset.path,
outputPath,
inputPath,
input.path,
];
final String codePoints = iconTreeShakerData.codePoints.join(' ');
_logger.printTrace('Running font-subset: ${cmd.join(' ')}, '
......@@ -186,9 +203,7 @@ class IconTreeShaker {
fontSubsetProcess.stdin.writeln(codePoints);
await fontSubsetProcess.stdin.flush();
await fontSubsetProcess.stdin.close();
} on Exception catch (_) {
// handled by checking the exit code.
} on OSError catch (_) { // ignore: dead_code_on_catch_subtype
} on Exception {
// handled by checking the exit code.
}
......
......@@ -136,7 +136,7 @@ class BuildBundleCommand extends BuildSubCommand {
extraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions,
fileSystemScheme: stringArg('filesystem-scheme'),
fileSystemRoots: stringsArg('filesystem-root'),
treeShakeIcons: boolArg('tree-shake-icons'),
treeShakeIcons: buildInfo.treeShakeIcons,
);
return FlutterCommandResult.success();
}
......
......@@ -19,7 +19,7 @@ class BuildWebCommand extends BuildSubCommand {
BuildWebCommand({
@required bool verboseHelp,
}) {
addTreeShakeIconsFlag();
addTreeShakeIconsFlag(enabledByDefault: false);
usesTargetOption();
usesPubOption();
addBuildModeFlags(excludeDebug: true);
......
......@@ -425,10 +425,13 @@ abstract class FlutterCommand extends Command<void> {
);
}
void addTreeShakeIconsFlag() {
void addTreeShakeIconsFlag({
bool enabledByDefault
}) {
argParser.addFlag('tree-shake-icons',
negatable: true,
defaultsTo: kIconTreeShakerEnabledDefault,
defaultsTo: enabledByDefault
?? kIconTreeShakerEnabledDefault,
help: 'Tree shake icon fonts so that only glyphs used by the application remain.',
);
}
......@@ -579,8 +582,12 @@ abstract class FlutterCommand extends Command<void> {
'combination with "--${FlutterOptions.kSplitDebugInfoOption}"',
);
}
final BuildMode buildMode = getBuildMode();
final bool treeShakeIcons = argParser.options.containsKey('tree-shake-icons')
&& buildMode.isPrecompiled
&& boolArg('tree-shake-icons');
return BuildInfo(getBuildMode(),
return BuildInfo(buildMode,
argParser.options.containsKey('flavor')
? stringArg('flavor')
: null,
......@@ -601,9 +608,7 @@ abstract class FlutterCommand extends Command<void> {
buildName: argParser.options.containsKey('build-name')
? stringArg('build-name')
: null,
treeShakeIcons: argParser.options.containsKey('tree-shake-icons')
? boolArg('tree-shake-icons')
: kIconTreeShakerEnabledDefault,
treeShakeIcons: treeShakeIcons,
splitDebugInfoPath: splitDebugInfoPath,
dartObfuscation: dartObfuscation,
dartDefines: argParser.options.containsKey(FlutterOptions.kDartDefinesOption)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment