-
-
Save ClementBeal/10ed98576d4069aae74ef6ef11a8788b to your computer and use it in GitHub Desktop.
Optimize flutter web build
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from collections import Counter | |
from decimal import Decimal | |
import gzip | |
from pathlib import Path | |
import re | |
from fractions import Fraction | |
file_data = Path(r"./my_app/build/web/main.dart.js").read_text() | |
initial_file_size = len(file_data) | |
initial_compressed_file_size = len(gzip.compress(bytes(file_data, encoding="utf-8"))) | |
print(f"Initial size : {initial_file_size:,}") | |
print(f"Initial compressed size : {initial_compressed_file_size:,}") | |
print("--------------") | |
def is_float(a: str): | |
try: | |
float(a) | |
return True | |
except ValueError: | |
return False | |
def variable_generator(): | |
i, j, k = 97, 97, 97 | |
while True: | |
yield "".join(chr(a) for a in [i, j, k]) | |
k = k + 1 | |
if k > 122: | |
k = 97 | |
j += 1 | |
if j > 122: | |
j = 97 | |
i += 1 | |
custom_variable_generator = variable_generator() | |
def true_false_optimizer(data): | |
def replace_true_false(match): | |
return "!0" if match.group(1) == "true" else "!1" | |
return re.sub(r"(true|false)", replace_true_false, data) | |
def indentation_optimizer(data): | |
return re.sub(r"^\s+", "", data, flags=re.MULTILINE) | |
def fraction_optimizer(data): | |
def replace_with_fraction(match): | |
string_value = match.group(1) | |
fraction = Fraction(string_value).as_integer_ratio() | |
if len(str(fraction)) < len(string_value): | |
return str(fraction) | |
return string_value | |
return re.sub(r"(\d+\.\d+)", replace_with_fraction, data) | |
def float_leading_zero_optimizer(data): | |
def remove_leading_zero(match): | |
return "." + match.group(1) | |
return re.sub(r"0\.(\d+)", remove_leading_zero, data) | |
def empty_constructor_optimizer(data): | |
return re.sub(r"(new \w+\.\w+)\(\)", lambda match: match.group(1), data) | |
def repeated_literals_optimizer(data): | |
all_integers = re.findall(r"\W(\d+)", data) | |
all_strings = re.findall(r'".+?"', data) | |
all_decimals = re.findall(r"\d+\.\d+", data) | |
counter = Counter([*all_integers, *all_strings, *all_decimals]) | |
total_new_variables = [] | |
replacements = {} | |
for value, repetition in counter.items(): | |
if repetition <= 1: | |
continue | |
is_number = value.isnumeric() or is_float(value) | |
if is_number: | |
# we cannot replace 0.3 with AAA, we don't have gains | |
if (len(str(value))) < 4: | |
continue | |
else: | |
# remove the wrapping "" | |
short_value = value[1:-1] | |
if len(short_value) < 4: | |
continue | |
variable_name = next(custom_variable_generator) | |
total_new_variables.append(f"{variable_name}={value}") | |
replacements[value] = variable_name | |
for value, replacement in sorted( | |
replacements.items(), key=lambda x: len(str(x)), reverse=True | |
): | |
regex_value = rf"{re.escape(value)}" if isinstance(value, str) else rf"{value}" | |
data = re.sub(regex_value, replacement, data) | |
new_variables_list = f"var {','.join(total_new_variables)};\n" | |
data = new_variables_list + data | |
return data | |
def reduce_null_arguments_optimizer(data): | |
def reduce_null_arguments(match): | |
# 22 chars at least | |
# ...Array(X).fill(null) | |
# | |
# null,null,null,null | |
has_final_comma = match.group(0)[-1] == "," | |
nb_null = match.group(0).count("null") | |
a = f"...Array({nb_null}).fill(null)" | |
if has_final_comma: | |
a += "," | |
if len(a) < len(match.group(0)): | |
return a | |
return match.group(0) | |
return re.sub(r"(null,?){3,}", reduce_null_arguments, data) | |
def flip_null_condition_optimizer(data): | |
def _flip(match): | |
return f"if(null=={match.group(1)})" | |
return re.sub(r"if\((\S+)==null\)", _flip, data) | |
def use_scientist_notation(data): | |
def _replace(match): | |
value = match.group(1) | |
print(match.groups(0)) | |
if value.isnumeric(): | |
i = 0 | |
num = Decimal(value) | |
while num % 10 == 0 and num != 0: | |
i += 1 | |
num = num / 10 | |
if i > 2: | |
return "{num}e{i}" | |
return value | |
return value | |
return re.sub(r"(\W)((\d+)?(\.)?(\d+))(\W)", _replace, data) | |
def multiplication_zero_optimizer(data): | |
def _replace(match): | |
return match.group(0)[0] | |
return re.sub(r"[\+\-\*\\](\w+\*0[^0123456789\.x])", _replace, data) | |
def nullish_coalescing_assignment_optimizer(data): | |
""" | |
`??=` operator instead of `if(a==null)a=<...> | |
""" | |
def _replace(match): | |
if len(match.groups(4)) == "else": | |
return match.group(0) | |
v1 = match.group(1) | |
v2 = match.group(2) | |
v3 = match.group(3) | |
if v1 == v2: | |
return f"{v1}??={v3}\n" | |
return match.group(0) | |
return re.sub( | |
r"^if\((.+)==null\)(.+)=(.+)\n(else)?", _replace, data, flags=re.MULTILINE | |
) | |
def useless_null_condition_optimizer(data): | |
""" | |
`s==null?null:s` -> `s` | |
""" | |
def _replace(match): | |
v1 = match.group(1).strip() | |
v2 = match.group(2) | |
if v1 == v2: | |
return f"return {v1}" | |
return match.group(0) | |
return re.sub(r"return ([^{]+)==null\?null:([^}]+)", _replace, data) | |
def equal_0_condition_optimizer(data): | |
""" | |
`if(o===0)return` -> `if(!o)` | |
`if(o!==0)return` -> `if(!!o)` | |
The regex is wrong. It's an operation easier to do with the AST | |
I add the `!` and `!!` operator to make it close to the final result | |
""" | |
def _replace(match): | |
# condition_name = match.group(1) | |
operator = match.group(1) | |
if operator == "===": | |
return "!" | |
else: | |
return "!!" | |
return re.sub(r"([!=]==)0", _replace, data) | |
def comma_endline_optimizer(data): | |
return re.sub(r",\n", ",", data, flags=re.MULTILINE) | |
def reduce_float_length(data): | |
def _replace(match): | |
return f"{float(match.group(0)):8f}" | |
return re.sub(r"\d+\.\d{8,20}", _replace, data) | |
def ternary_condition_optimizer(data): | |
def _replace(match): | |
variable_condition = match.group(1) | |
v1 = match.group(2) | |
v2 = match.group(3) | |
if variable_condition == v2: | |
return f"{variable_condition}??{v1}" | |
return match.group(0) | |
return re.sub(r"(\w+)==null\?(.+):(\w+)", _replace, data) | |
optimizers = [ | |
true_false_optimizer, | |
indentation_optimizer, | |
empty_constructor_optimizer, | |
fraction_optimizer, | |
float_leading_zero_optimizer, | |
# repeated_literals_optimizer, | |
# reduce_null_arguments_optimizer, | |
# flip_null_condition_optimizer, | |
multiplication_zero_optimizer, | |
# use_scientist_notation, | |
nullish_coalescing_assignment_optimizer, | |
useless_null_condition_optimizer, | |
equal_0_condition_optimizer, | |
comma_endline_optimizer, | |
reduce_float_length, | |
ternary_condition_optimizer, | |
] | |
for optimizer in optimizers: | |
initial_length = len(file_data) | |
file_data = optimizer(file_data) | |
final_length = len(file_data) | |
print(f"{optimizer.__name__} -> {initial_length-final_length}") | |
print("\n-------------\n") | |
final_file_size = len(file_data) | |
saving = initial_file_size - final_file_size | |
print(f"We saved {saving:,} bytes") | |
compressed_length = len(gzip.compress(bytes(file_data, encoding="utf-8"))) | |
print(f"Compressed : {compressed_length:,}") | |
with open("output.js", "w") as f: | |
f.write(file_data) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment