///|
pub struct WechatBuildBudget {
max_pages : Int
max_files : Int
max_total_bytes : Int
max_wxml_bytes : Int
max_wxss_bytes : Int
max_js_bytes : Int
max_initial_data_bytes : Int
max_event_patch_bytes : Int
max_event_patch_operations : Int
max_page_first_screen_bytes : Int
max_page_update_payload_bytes : Int
max_page_update_operations : Int
} derive(Debug, Eq)
///|
pub struct WechatBuildReport {
page_count : Int
file_count : Int
total_bytes : Int
wxml_bytes : Int
wxss_bytes : Int
js_bytes : Int
initial_data_bytes : Int
event_patch_bytes : Int
event_patch_operation_count : Int
missing_event_patch_count : Int
orphan_event_patch_count : Int
max_page_first_screen_bytes : Int
max_page_update_payload_bytes : Int
max_page_update_operation_count : Int
recommended_main_package_bytes : Int
package_size_advisory : String
diagnostics : Array[String]
summary : String
} derive(Debug, Eq)
///|
pub fn default_build_budget() -> WechatBuildBudget {
{
max_pages: 32,
max_files: 16,
max_total_bytes: 2 * 1024 * 1024,
max_wxml_bytes: 65536,
max_wxss_bytes: 65536,
max_js_bytes: 65536,
max_initial_data_bytes: 65536,
max_event_patch_bytes: 8192,
max_event_patch_operations: 64,
max_page_first_screen_bytes: 98304,
max_page_update_payload_bytes: 8192,
max_page_update_operations: 16,
}
}
///|
pub fn build_budget(
max_pages~ : Int,
max_files~ : Int,
max_total_bytes~ : Int,
max_wxml_bytes~ : Int,
max_wxss_bytes~ : Int,
max_js_bytes~ : Int,
max_initial_data_bytes~ : Int,
max_event_patch_bytes~ : Int,
max_event_patch_operations~ : Int,
max_page_first_screen_bytes~ : Int,
max_page_update_payload_bytes~ : Int,
max_page_update_operations~ : Int,
) -> WechatBuildBudget {
{
max_pages,
max_files,
max_total_bytes,
max_wxml_bytes,
max_wxss_bytes,
max_js_bytes,
max_initial_data_bytes,
max_event_patch_bytes,
max_event_patch_operations,
max_page_first_screen_bytes,
max_page_update_payload_bytes,
max_page_update_operations,
}
}
///|
pub fn report_project(
project : WechatProject,
_runtime : WechatRuntime,
) -> WechatBuildReport {
report_project_pages(project.files, project.pages, default_build_budget())
}
///|
pub fn report_project_with_budget(
project : WechatProject,
_runtime : WechatRuntime,
budget : WechatBuildBudget,
) -> WechatBuildReport {
report_project_pages(project.files, project.pages, budget)
}
///|
pub fn report_project_pages(
files : Array[WechatFile],
pages : Array[WechatProjectPage],
budget : WechatBuildBudget,
) -> WechatBuildReport {
report_files(files, pages, budget)
}
///|
fn report_files(
files : Array[WechatFile],
pages : Array[WechatProjectPage],
budget : WechatBuildBudget,
) -> WechatBuildReport {
let diagnostics : Array[String] = []
let mut total = 0
let mut wxml = 0
let mut wxss = 0
let mut js = 0
for file in files {
let bytes = materialized_file_bytes(file, pages)
total += bytes
if file.path.has_suffix(".wxml") {
wxml += bytes
} else if file.path.has_suffix(".wxss") {
wxss += bytes
} else if file.path.has_suffix(".js") {
js += bytes
}
}
let initial = initial_data_bytes(pages)
let event_patch = event_patch_bytes(pages)
let event_patch_operations = event_patch_operation_count(pages)
let recommended_main_package_bytes = recommended_main_package_bytes(budget)
let package_size_advisory = package_size_advisory(
total,
budget.max_total_bytes,
recommended_main_package_bytes,
)
let (max_first_screen, max_update_payload, max_update_operations) = push_page_performance_diagnostics(
diagnostics, files, pages, budget,
)
push_page_manifest_diagnostics(diagnostics, pages)
let (missing_event_patch_count, orphan_event_patch_count) = count_runtime_event_diagnostics(
pages,
)
push_over_budget(
diagnostics,
"wechat-page-count-over-budget",
pages.length(),
budget.max_pages,
)
push_over_budget(
diagnostics,
"wechat-file-count-over-budget",
files.length(),
budget.max_files,
)
push_over_budget(
diagnostics,
"wechat-total-bytes-over-budget",
total,
budget.max_total_bytes,
)
push_over_budget(
diagnostics,
"wechat-wxml-bytes-over-budget",
wxml,
budget.max_wxml_bytes,
)
push_over_budget(
diagnostics,
"wechat-wxss-bytes-over-budget",
wxss,
budget.max_wxss_bytes,
)
push_over_budget(
diagnostics,
"wechat-js-bytes-over-budget",
js,
budget.max_js_bytes,
)
push_over_budget(
diagnostics,
"wechat-initial-data-bytes-over-budget",
initial,
budget.max_initial_data_bytes,
)
push_over_budget(
diagnostics,
"wechat-event-patch-bytes-over-budget",
event_patch,
budget.max_event_patch_bytes,
)
push_over_budget(
diagnostics,
"wechat-event-patch-operations-over-budget",
event_patch_operations,
budget.max_event_patch_operations,
)
{
page_count: pages.length(),
file_count: files.length(),
total_bytes: total,
wxml_bytes: wxml,
wxss_bytes: wxss,
js_bytes: js,
initial_data_bytes: initial,
event_patch_bytes: event_patch,
event_patch_operation_count: event_patch_operations,
missing_event_patch_count,
orphan_event_patch_count,
max_page_first_screen_bytes: max_first_screen,
max_page_update_payload_bytes: max_update_payload,
max_page_update_operation_count: max_update_operations,
recommended_main_package_bytes,
package_size_advisory,
diagnostics,
summary: "Bunnia WeChat build report: pages=\{pages.length()} files=\{files.length()} total=\{total} hard_package_limit=\{budget.max_total_bytes} recommended_package_limit=\{recommended_main_package_bytes} package_size=\{package_size_advisory} wxml=\{wxml} wxss=\{wxss} js=\{js} initial_data=\{initial} event_patches=\{event_patch} event_patch_ops=\{event_patch_operations} missing_event_patches=\{missing_event_patch_count} orphan_event_patches=\{orphan_event_patch_count} max_first_screen=\{max_first_screen} max_update_payload=\{max_update_payload} max_update_ops=\{max_update_operations} diagnostics=\{diagnostics.length()}",
}
}
///|
fn recommended_main_package_bytes(budget : WechatBuildBudget) -> Int {
let wechat_recommended = 1536 * 1024
if budget.max_total_bytes < wechat_recommended {
budget.max_total_bytes
} else {
wechat_recommended
}
}
///|
fn package_size_advisory(
total : Int,
hard_limit : Int,
recommended_limit : Int,
) -> String {
if total > hard_limit {
"over-hard-limit"
} else if total > recommended_limit {
"split-recommended"
} else {
"within-headroom"
}
}
///|
fn push_page_manifest_diagnostics(
diagnostics : Array[String],
pages : Array[WechatProjectPage],
) -> Unit {
for page in pages {
for item in page_manifest_diagnostics(page) {
diagnostics.push(item)
}
}
}
///|
fn count_runtime_event_diagnostics(
pages : Array[WechatProjectPage],
) -> (Int, Int) {
let mut missing_count = 0
let mut orphan_count = 0
for page in pages {
for message in page.page.plan.event_messages {
if !runtime_has_patch(page.runtime, message) {
missing_count += 1
}
}
for patch in page.runtime.event_patches {
if !plan_has_event(page.page.plan, patch.message) {
orphan_count += 1
}
}
}
(missing_count, orphan_count)
}
///|
fn event_patch_operation_count(pages : Array[WechatProjectPage]) -> Int {
let mut total = 0
for page in pages {
total += report_page_update_operation_count(page)
}
total
}
///|
fn runtime_has_patch(runtime : WechatRuntime, message : String) -> Bool {
for item in runtime.event_patches {
if item.message == message {
return true
}
}
false
}
///|
fn plan_has_event(plan : @core.RenderPlan, message : String) -> Bool {
for item in plan.event_messages {
if item == message {
return true
}
}
false
}
///|
fn push_page_performance_diagnostics(
diagnostics : Array[String],
files : Array[WechatFile],
pages : Array[WechatProjectPage],
budget : WechatBuildBudget,
) -> (Int, Int, Int) {
let mut max_first_screen = 0
let mut max_update_payload = 0
let mut max_update_operations = 0
for page in pages {
let first_screen = page_first_screen_bytes(page, files)
let update_payload = page_update_payload_bytes(page)
let update_operations = report_page_update_operation_count(page)
if first_screen > max_first_screen {
max_first_screen = first_screen
}
if update_payload > max_update_payload {
max_update_payload = update_payload
}
if update_operations > max_update_operations {
max_update_operations = update_operations
}
push_page_over_budget(
diagnostics,
"wechat-page-first-screen-bytes-over-budget",
page.route,
first_screen,
budget.max_page_first_screen_bytes,
)
push_page_over_budget(
diagnostics,
"wechat-page-update-payload-bytes-over-budget",
page.route,
update_payload,
budget.max_page_update_payload_bytes,
)
push_page_over_budget(
diagnostics,
"wechat-page-update-operations-over-budget",
page.route,
update_operations,
budget.max_page_update_operations,
)
}
(max_first_screen, max_update_payload, max_update_operations)
}
///|
fn page_first_screen_bytes(
page : WechatProjectPage,
files : Array[WechatFile],
) -> Int {
route_file_bytes(page.route, files) +
shared_first_screen_bytes(page.route, files) +
page.runtime.initial_data_json.length()
}
///|
fn shared_first_screen_bytes(route : String, files : Array[WechatFile]) -> Int {
let mut total = 0
for file in files {
if file.path == "app.wxss" || file.path == "bunnia.runtime.js" {
total += file.content.length()
} else if file.path == "bunnia.backend.js" &&
route_requires_backend(route, files) {
total += file.content.length()
}
}
total
}
///|
fn route_requires_backend(route : String, files : Array[WechatFile]) -> Bool {
for file in files {
if file.path == "\{route}.js" && file.content.contains("bunnia.backend.js") {
return true
}
}
false
}
///|
fn page_update_payload_bytes(page : WechatProjectPage) -> Int {
page_event_patch_bytes(page)
}
///|
fn report_page_update_operation_count(page : WechatProjectPage) -> Int {
let mut total = 0
for item in page.runtime.event_patches {
if page.page.plan.event_messages.contains(item.message) {
total += item.patches.length()
}
}
total
}
///|
fn initial_data_bytes(items : Array[WechatProjectPage]) -> Int {
let mut total = 0
for item in items {
total += item.runtime.initial_data_json.length()
}
total
}
///|
fn event_patch_bytes(pages : Array[WechatProjectPage]) -> Int {
let mut total = 0
for page in pages {
total += page_event_patch_bytes(page)
}
total
}
///|
fn push_over_budget(
diagnostics : Array[String],
label : String,
actual : Int,
limit : Int,
) -> Unit {
if actual > limit {
diagnostics.push("\{label}:\{actual}/\{limit}")
}
}
///|
fn push_page_over_budget(
diagnostics : Array[String],
label : String,
route : String,
actual : Int,
limit : Int,
) -> Unit {
if actual > limit {
diagnostics.push("\{label}:\{route}:\{actual}/\{limit}")
}
}