Skip to content

Instantly share code, notes, and snippets.

@ClementBeal
Created April 5, 2024 07:32
Show Gist options
  • Save ClementBeal/10ed98576d4069aae74ef6ef11a8788b to your computer and use it in GitHub Desktop.
Save ClementBeal/10ed98576d4069aae74ef6ef11a8788b to your computer and use it in GitHub Desktop.
Optimize flutter web build
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