Skip to content

Commit 3b8a6d8

Browse files
Merge remote-tracking branch 'origin/master' into upgrade-analyzer
2 parents 359e437 + 16b9057 commit 3b8a6d8

66 files changed

Lines changed: 943 additions & 38 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44
- Allow analyzer 8.x, 9.x, 10.x
5+
- Remove dependency on `transformer_utils`
56

67
## 5.5.0
78
- [#989] Optimize generated code to decrease dart2js compile size, saving ~577 bytes per component (when using `-03 --csp --minify`)

benchmark/dart2js_output.dart

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import 'dart:io';
2+
3+
import 'package:args/command_runner.dart';
4+
import 'package:meta/meta.dart';
5+
6+
import 'dart2js_output/compile.dart';
7+
import 'dart2js_output/dart2js_normalize.dart';
8+
import 'dart2js_output/dependency.dart';
9+
import 'dart2js_output/logging.dart';
10+
import 'dart2js_output/source.dart' as source;
11+
12+
Future<void> main(List<String> args) async {
13+
final runner = CommandRunner(
14+
'benchmark-dart2js-output',
15+
'Runs various dart2js output benchmarks and comparisons.'
16+
' Useful for debugging/validating how changes to over_react affect dart2js output.')
17+
..addCommand(CompareSizeCommand())
18+
..addCommand(CompareCodeCommand())
19+
..addCommand(GetCodeCommand());
20+
21+
await runner.run(args);
22+
}
23+
24+
abstract class BaseCommand extends Command {
25+
BaseCommand() {
26+
argParser.addFlag('verbose', defaultsTo: true, negatable: true);
27+
}
28+
29+
@override
30+
@mustCallSuper
31+
void run() {
32+
initLogging(verbose: argResults!['verbose'] as bool);
33+
}
34+
}
35+
36+
abstract class CompareCommand extends BaseCommand {
37+
CompareCommand() {
38+
argParser.addOption(
39+
'head',
40+
help: 'Head over_react dependency to compare to the base.'
41+
' Supports dependencies entry in JSON format, or `git:<ref>` shorthand.'
42+
' Defaults to the enclosing local working copy of over_react.',
43+
defaultsTo: localPathDepString,
44+
);
45+
argParser.addOption(
46+
'base',
47+
help: 'Base over_react dependency to compare against.'
48+
' Supports dependencies entry in JSON format, or `git:<ref>` shorthand.',
49+
defaultsTo: originHeadDepString,
50+
);
51+
}
52+
53+
dynamic get _baseDep => parseOverReactDependency(argResults!['base'] as String);
54+
55+
dynamic get _headDep => parseOverReactDependency(argResults!['head'] as String);
56+
}
57+
58+
class CompareSizeCommand extends CompareCommand {
59+
@override
60+
String get description =>
61+
'Compares the optimized, minified size of dart2js output for a benchmark React component between two over_react versions.';
62+
63+
@override
64+
String get name => 'compare-size';
65+
66+
@override
67+
Future<void> run() async {
68+
super.run();
69+
final baseSize = getComponentAndUsageSize(overReactDep: _baseDep);
70+
final headSize = getComponentAndUsageSize(overReactDep: _headDep);
71+
print('Base size: ${await baseSize} bytes');
72+
print('Head size: ${await headSize} bytes');
73+
print('(Head size) - (base size):'
74+
' ${(await headSize) - (await baseSize)} bytes');
75+
}
76+
}
77+
78+
class CompareCodeCommand extends CompareCommand {
79+
@override
80+
String get name => 'compare-code';
81+
82+
@override
83+
String get description =>
84+
'Compares the optimized, non-minified dart2js output for a benchmark React component between two over_react versions.'
85+
'\nOutputs in a Git diff format.'
86+
'\nCompiled code is normalized before comparison for better diffing.';
87+
88+
@override
89+
Future<void> run() async {
90+
super.run();
91+
final diff = await compareCodeAcrossVersions(
92+
source.componentBenchmark(
93+
componentCount: 1,
94+
propsCount: 5,
95+
),
96+
overReactDep1: _baseDep,
97+
overReactDep2: _headDep,
98+
color: stdioType(stdout) == StdioType.terminal,
99+
);
100+
if (diff.trim().isEmpty) {
101+
print('(No difference in dart2js output between base and head)');
102+
} else {
103+
print(diff);
104+
}
105+
}
106+
}
107+
108+
class GetCodeCommand extends BaseCommand {
109+
@override
110+
String get name => 'get-code';
111+
112+
@override
113+
String get description =>
114+
'Displays the optimized, non-minified dart2js output for a benchmark React component.'
115+
'\nOutputs in a Git diff format, showing output changes when adding a component.'
116+
'\nCompiled code is normalized before comparison for better diffing.';
117+
118+
GetCodeCommand() {
119+
argParser.addOption(
120+
'dependency',
121+
help: 'over_react dependency to compile with.'
122+
' Supports dependencies entry in JSON format, or `git:<ref>` shorthand.'
123+
' Defaults to the enclosing local working copy of over_react.',
124+
defaultsTo: localPathDepString,
125+
);
126+
}
127+
128+
dynamic get _dep => parseOverReactDependency(argResults!['dependency'] as String);
129+
130+
@override
131+
Future<void> run() async {
132+
super.run();
133+
print(await getCompiledComponentCode(
134+
overReactDep: _dep,
135+
color: stdioType(stdout) == StdioType.terminal,
136+
));
137+
}
138+
}
139+
140+
Future<String> compareCodeAcrossVersions(
141+
String code, {
142+
required dynamic overReactDep1,
143+
required dynamic overReactDep2,
144+
bool color = false,
145+
}) async {
146+
final results1 = compileOverReactProgram(
147+
webFilesByName: {'main.dart': code},
148+
overReactDep: overReactDep1,
149+
minify: false,
150+
);
151+
final results2 = compileOverReactProgram(
152+
webFilesByName: {'main.dart': code},
153+
overReactDep: overReactDep2,
154+
minify: false,
155+
);
156+
157+
return gitDiffNoIndex(
158+
createNormalizedDart2jsFile((await results1).getCompiledDart2jsFile()).path,
159+
createNormalizedDart2jsFile((await results2).getCompiledDart2jsFile()).path,
160+
color: color,
161+
);
162+
}
163+
164+
Future<String> getCompiledComponentCode({
165+
dynamic overReactDep,
166+
bool color = false,
167+
}) async {
168+
const baselineComponentCount = 2;
169+
const propsCount = 3;
170+
171+
final result = await compileOverReactProgram(webFilesByName: {
172+
'baseline.dart': source.componentBenchmark(
173+
componentCount: baselineComponentCount,
174+
propsCount: propsCount,
175+
),
176+
'additional.dart': source.componentBenchmark(
177+
componentCount: baselineComponentCount + 1,
178+
propsCount: propsCount,
179+
),
180+
}, overReactDep: overReactDep, minify: false);
181+
182+
final baselineCompiledFile = result.getCompiledDart2jsFile('baseline.dart');
183+
final additionalCompiledFile = result.getCompiledDart2jsFile('additional.dart');
184+
185+
return gitDiffNoIndex(
186+
createNormalizedDart2jsFile(baselineCompiledFile).path,
187+
createNormalizedDart2jsFile(additionalCompiledFile).path,
188+
color: color,
189+
);
190+
}
191+
192+
File createNormalizedDart2jsFile(File f) {
193+
return File(f.path + '.normalized.js')
194+
..writeAsStringSync(normalizeDart2jsContents(f.readAsStringSync()));
195+
}
196+
197+
Future<String> gitDiffNoIndex(
198+
String file1,
199+
String file2, {
200+
int contextLines = 1,
201+
bool color = false,
202+
}) async {
203+
final result = await Process.run('git', [
204+
'diff',
205+
'--no-index',
206+
'-U$contextLines',
207+
if (color) '--color',
208+
file1,
209+
file2,
210+
]);
211+
212+
if (result.exitCode == 0 || result.exitCode == 1) {
213+
return result.stdout as String;
214+
}
215+
216+
throw Exception('Error diffing files. Exit code: ${result.exitCode} stderr: $stderr');
217+
}
218+
219+
/// Gets the total size of a single test component, plus usage that sets all props,
220+
/// and render that reads all props.
221+
///
222+
/// Since it contains this extra usage and render code, it's mainly useful when
223+
/// comparing across versions, and shouldn't by itself be used as a number that
224+
/// represents "the cost of declaring a component"."
225+
Future<int> getComponentAndUsageSize({
226+
dynamic overReactDep,
227+
}) async {
228+
const baselineComponentCount = 100;
229+
const propsCount = 5;
230+
231+
final result = await compileOverReactProgram(webFilesByName: {
232+
'baseline.dart': source.componentBenchmark(
233+
componentCount: baselineComponentCount,
234+
propsCount: propsCount,
235+
),
236+
'additional.dart': source.componentBenchmark(
237+
componentCount: baselineComponentCount + 1,
238+
propsCount: propsCount,
239+
),
240+
}, overReactDep: overReactDep);
241+
242+
final baselineFileSize = result.getCompiledDart2jsFile('baseline.dart').statSync().size;
243+
final additionalFileSize = result.getCompiledDart2jsFile('additional.dart').statSync().size;
244+
validateFileSize(baselineFileSize);
245+
validateFileSize(additionalFileSize);
246+
247+
return additionalFileSize - baselineFileSize;
248+
}
249+
250+
void validateFileSize(int actualSizeInBytes) {
251+
// Arbitrary minimum expected size to help ensure the test setup is correct.
252+
//
253+
// Value derived from the compiled size of the following Dart program:
254+
// import 'package:over_react/over_react.dart';
255+
// void main() { Dom.div()(); }
256+
const minimumExpectedSizeInBytes = 144339;
257+
258+
if (actualSizeInBytes < minimumExpectedSizeInBytes) {
259+
throw Exception('Expected compiled size to be larger,'
260+
' at least $minimumExpectedSizeInBytes bytes.'
261+
' Was: $actualSizeInBytes bytes.');
262+
}
263+
}

0 commit comments

Comments
 (0)