Commit bfff9e47 authored by DatHV's avatar DatHV
Browse files

update

parent e838a036
...@@ -78,13 +78,25 @@ echo "🔨 Flutter build web (release, MAXIMUM optimization for T3 delivery)..." ...@@ -78,13 +78,25 @@ echo "🔨 Flutter build web (release, MAXIMUM optimization for T3 delivery)..."
# --pwa-strategy=none: Tắt PWA service worker (giảm kích thước) # --pwa-strategy=none: Tắt PWA service worker (giảm kích thước)
# --no-source-maps: Không tạo source maps (giảm kích thước build) # --no-source-maps: Không tạo source maps (giảm kích thước build)
# --tree-shake-icons: Chỉ include icons được sử dụng (giảm kích thước fonts) # --tree-shake-icons: Chỉ include icons được sử dụng (giảm kích thước fonts)
# --dart-define=FLUTTER_WEB_USE_SKIA=false: Force HTML renderer (không dùng CanvasKit) # --dart-define=FLUTTER_WEB_USE_SKIA: Use CanvasKit if KEEP_CANVASKIT=1, otherwise HTML renderer
flutter build web \ if [ "${KEEP_CANVASKIT:-0}" = "1" ]; then
echo " → Building with CanvasKit (KEEP_CANVASKIT=1)..."
flutter build web \
--release \
--pwa-strategy=none \
--no-source-maps \
--tree-shake-icons \
--dart-define=FLUTTER_WEB_USE_SKIA=true \
--dart-define=FLUTTER_WEB_USE_SKWASM=false
else
echo " → Building with HTML renderer (no CanvasKit)..."
flutter build web \
--release \ --release \
--pwa-strategy=none \ --pwa-strategy=none \
--no-source-maps \ --no-source-maps \
--tree-shake-icons \ --tree-shake-icons \
--dart-define=FLUTTER_WEB_USE_SKIA=false --dart-define=FLUTTER_WEB_USE_SKIA=false
fi
copy_x_app_sdk copy_x_app_sdk
...@@ -111,14 +123,163 @@ content = re.sub( ...@@ -111,14 +123,163 @@ content = re.sub(
flags=re.IGNORECASE flags=re.IGNORECASE
) )
# Ensure flutterConfiguration.renderer is set # Set renderer and canvasKitBaseUrl based on KEEP_CANVASKIT
if 'window.flutterConfiguration.renderer' not in content: import os
# Add renderer config before flutter_bootstrap.js loads keep_canvaskit = os.environ.get('KEEP_CANVASKIT') == '1'
if keep_canvaskit:
# When KEEP_CANVASKIT=1, use CanvasKit with local path ONLY (no CDN fallback)
# Remove all existing CanvasKit config to start fresh
content = re.sub(
r'window\.flutterConfiguration\.renderer\s*=\s*[^;]+;',
'',
content
)
content = re.sub(
r'window\.flutterConfiguration\.canvasKitBaseUrl\s*=\s*[^;]+;',
'',
content
)
content = re.sub(
r'window\.flutterConfiguration\.useLocalCanvasKit\s*=\s*[^;]+;',
'',
content
)
# Ensure flutterConfiguration is initialized FIRST, before any other scripts
# Find the first <script> tag and add config right after it
if not re.search(r'window\.flutterConfiguration\s*=\s*window\.flutterConfiguration\s*\|\|\s*\{\};', content):
# Add initialization if missing - insert right after first script tag or in head
if re.search(r'<script[^>]*>', content):
content = re.sub(
r'(<script[^>]*>)',
r'\1\n window.flutterConfiguration = window.flutterConfiguration || {};',
content,
count=1
)
else:
# Add before closing head if no script tag found
content = re.sub(
r'(</head>)',
r' <script>\n window.flutterConfiguration = window.flutterConfiguration || {};\n </script>\n\1',
content
)
# Set CanvasKit config - MUST be set before flutter_bootstrap.js loads
# Use relative path 'canvaskit/' (will resolve to ./canvaskit/ from document base)
# Set useLocalCanvasKit to prevent any CDN fallback
config_block = """ window.flutterConfiguration.renderer = 'canvaskit';
window.flutterConfiguration.canvasKitBaseUrl = 'canvaskit/';
window.flutterConfiguration.useLocalCanvasKit = true;
// Prevent CDN fallback - force local only
window.flutterConfiguration.canvasKitVariant = 'chromium';"""
# Insert config right after flutterConfiguration initialization
if re.search(r'window\.flutterConfiguration\s*=\s*window\.flutterConfiguration\s*\|\|\s*\{\};', content):
content = re.sub(
r'(window\.flutterConfiguration\s*=\s*window\.flutterConfiguration\s*\|\|\s*\{\};)',
r'\1\n' + config_block,
content
)
else:
# Fallback: add right after first script tag
content = re.sub(
r'(<script[^>]*>\s*[^<]*)',
r'\1\n' + config_block,
content,
count=1
)
# Add script to redirect CDN requests to local - MUST run before flutter_bootstrap.js
redirect_cdn_script = """ <!-- Redirect CanvasKit CDN requests to local files -->
<script>
(function() {
// Redirect CDN URLs to local before Flutter loader runs
var originalFetch = window.fetch;
window.fetch = function(input, init) {
try {
var url = '';
if (typeof input === 'string') {
url = input;
} else if (input instanceof Request) {
url = input.url;
} else if (input && typeof input === 'object' && input.url) {
url = input.url;
}
if (url && url.includes('gstatic.com/flutter-canvaskit')) {
// Extract the path after the engine revision
// Pattern: https://www.gstatic.com/flutter-canvaskit/REVISION/path
var match = url.match(/flutter-canvaskit\/[^\/]+\/(.+)$/);
if (match) {
var localPath = 'canvaskit/' + match[1];
console.log('🔄 Redirecting CDN to local:', url.split('/').pop(), '->', localPath);
// Redirect to local path
if (typeof input === 'string') {
return originalFetch.call(this, localPath, init);
} else if (input instanceof Request) {
return originalFetch.call(this, new Request(localPath, input));
} else {
var newInput = Object.assign({}, input);
newInput.url = localPath;
return originalFetch.call(this, newInput.url || localPath, newInput);
}
}
}
} catch (e) {
console.error('Error in fetch redirect:', e);
}
return originalFetch.apply(this, arguments);
};
})();
</script>"""
# Add preload links for CanvasKit files to optimize loading
canvaskit_preloads = """ <!-- Preload CanvasKit for faster loading (local only) -->
<link rel="preload" href="canvaskit/chromium/canvaskit.wasm" as="fetch" crossorigin="anonymous">
<link rel="preload" href="canvaskit/chromium/canvaskit.js" as="script" crossorigin="anonymous">"""
# Add redirect CDN script and preloads before flutter_bootstrap.js script tag
if re.search(r'<script[^>]*src=["\']flutter_bootstrap\.js["\']', content):
content = re.sub(
r'(<script[^>]*src=["\']flutter_bootstrap\.js["\'][^>]*>)',
redirect_cdn_script + '\n' + canvaskit_preloads + '\n ' + r'\1',
content,
count=1
)
elif '</head>' in content:
# Add before closing head tag
content = re.sub(
r'(</head>)',
redirect_cdn_script + '\n' + ' ' + canvaskit_preloads + '\n' + r'\1',
content
)
else:
# When KEEP_CANVASKIT is not set, use HTML renderer
# Replace existing renderer if it's set to 'canvaskit' (handle both escaped and unescaped quotes)
content = re.sub(
r'window\.flutterConfiguration\.renderer\s*=\s*(\\?[\'"])canvaskit\1;',
"window.flutterConfiguration.renderer = 'html';",
content
)
# Add renderer if it doesn't exist
if 'window.flutterConfiguration.renderer' not in content:
content = re.sub( content = re.sub(
r'(window\.flutterConfiguration\s*=\s*window\.flutterConfiguration\s*\|\|\s*\{\};)', r'(window\.flutterConfiguration\s*=\s*window\.flutterConfiguration\s*\|\|\s*\{\};)',
r'\1\n window.flutterConfiguration.renderer = \'html\';', r'\1\n window.flutterConfiguration.renderer = \'html\';',
content content
) )
# Remove canvasKitBaseUrl and useLocalCanvasKit if they exist (not needed for HTML renderer)
content = re.sub(
r'window\.flutterConfiguration\.canvasKitBaseUrl\s*=\s*[^;]+;',
'',
content
)
content = re.sub(
r'window\.flutterConfiguration\.useLocalCanvasKit\s*=\s*[^;]+;',
'',
content
)
# Remove duplicate script tags for flutter_bootstrap.js (keep only the last one) # Remove duplicate script tags for flutter_bootstrap.js (keep only the last one)
script_tags = list(re.finditer(r'<script[^>]*src=["\']flutter_bootstrap\.js["\'][^>]*>', content, re.IGNORECASE)) script_tags = list(re.finditer(r'<script[^>]*src=["\']flutter_bootstrap\.js["\'][^>]*>', content, re.IGNORECASE))
...@@ -229,7 +390,11 @@ echo " 📁 Folder: ${folder_size}" ...@@ -229,7 +390,11 @@ echo " 📁 Folder: ${folder_size}"
echo "" echo ""
echo "📌 Delivery notes for T3:" echo "📌 Delivery notes for T3:"
echo " ✅ All assets are pre-compressed (gzip + brotli)" echo " ✅ All assets are pre-compressed (gzip + brotli)"
echo " ✅ CanvasKit removed (saves ~1.6MB)" if [ "${KEEP_CANVASKIT:-0}" = "1" ]; then
echo " ✅ CanvasKit included (local, not from CDN)"
else
echo " ✅ CanvasKit removed (saves ~1.6MB)"
fi
echo " ✅ Source maps removed (saves space)" echo " ✅ Source maps removed (saves space)"
echo " ✅ PWA disabled (simpler deployment)" echo " ✅ PWA disabled (simpler deployment)"
echo " ✅ Tree-shaken icons (only used icons included)" echo " ✅ Tree-shaken icons (only used icons included)"
......
...@@ -5,7 +5,7 @@ import 'package:firebase_messaging/firebase_messaging.dart'; ...@@ -5,7 +5,7 @@ import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:mypoint_flutter_app/firebase/push_notification.dart'; import 'package:mypoint_flutter_app/firebase/push_notification.dart';
import 'firebase_options.dart'; import '../core/platform/firebase/firebase_options.dart';
import 'notification_parse_payload.dart'; import 'notification_parse_payload.dart';
@pragma('vm:entry-point') // bắt buộc cho background isolate @pragma('vm:entry-point') // bắt buộc cho background isolate
......
...@@ -42,6 +42,54 @@ copy_x_app_sdk() { ...@@ -42,6 +42,54 @@ copy_x_app_sdk() {
echo "✅ Copied x-app-sdk bundle." echo "✅ Copied x-app-sdk bundle."
} }
compress_assets_for_dev() {
local dir="${PROJECT_ROOT}/build/web"
echo "🗜️ Compressing assets for 4G optimization (gzip + brotli)..."
# Gzip compression (level 9 = maximum)
if command -v gzip >/dev/null 2>&1; then
echo " → Creating .gz files (gzip -9)..."
find "${dir}" -type f \( -name '*.js' -o -name '*.wasm' -o -name '*.css' -o -name '*.json' \) ! -name '*.gz' ! -name '*.br' -exec gzip -9 -kf {} \;
echo " ✅ Gzip compression completed"
else
echo " ⚠️ gzip not available, skipping .gz artifacts"
fi
# Brotli compression (quality 11 = maximum) - better compression for 4G
if command -v brotli >/dev/null 2>&1; then
echo " → Creating .br files (brotli -q 11)..."
find "${dir}" -type f \( -name '*.js' -o -name '*.wasm' -o -name '*.css' -o -name '*.json' \) ! -name '*.gz' ! -name '*.br' -exec brotli -f -k -q 11 {} \;
echo " ✅ Brotli compression completed"
else
echo " ⚠️ brotli not available, skipping .br artifacts"
fi
# Show compression stats for canvaskit.wasm if it exists
local wasm_file="${dir}/canvaskit/chromium/canvaskit.wasm"
if [ -f "${wasm_file}" ]; then
local original_size=$(stat -f%z "${wasm_file}" 2>/dev/null || stat -c%s "${wasm_file}" 2>/dev/null || echo "0")
local gz_size=0
local br_size=0
if [ -f "${wasm_file}.gz" ]; then
gz_size=$(stat -f%z "${wasm_file}.gz" 2>/dev/null || stat -c%s "${wasm_file}.gz" 2>/dev/null || echo "0")
fi
if [ -f "${wasm_file}.br" ]; then
br_size=$(stat -f%z "${wasm_file}.br" 2>/dev/null || stat -c%s "${wasm_file}.br" 2>/dev/null || echo "0")
fi
echo ""
echo "📊 canvaskit.wasm compression stats:"
echo " Original: $(numfmt --to=iec-i --suffix=B ${original_size} 2>/dev/null || echo "${original_size} bytes")"
if [ "${gz_size}" -gt 0 ]; then
local gz_ratio=$(echo "scale=1; (${original_size} - ${gz_size}) * 100 / ${original_size}" | bc 2>/dev/null || echo "0")
echo " Gzip: $(numfmt --to=iec-i --suffix=B ${gz_size} 2>/dev/null || echo "${gz_size} bytes") (${gz_ratio}% smaller)"
fi
if [ "${br_size}" -gt 0 ]; then
local br_ratio=$(echo "scale=1; (${original_size} - ${br_size}) * 100 / ${original_size}" | bc 2>/dev/null || echo "0")
echo " Brotli: $(numfmt --to=iec-i --suffix=B ${br_size} 2>/dev/null || echo "${br_size} bytes") (${br_ratio}% smaller)"
fi
fi
}
build_web() { build_web() {
echo "📦 Getting Flutter packages..." echo "📦 Getting Flutter packages..."
flutter pub get flutter pub get
...@@ -71,6 +119,9 @@ class CORSRequestHandler(http.server.SimpleHTTPRequestHandler): ...@@ -71,6 +119,9 @@ class CORSRequestHandler(http.server.SimpleHTTPRequestHandler):
self.send_header('Access-Control-Allow-Origin', '*') self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, Origin') self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, Origin')
# Add cache headers for static assets to speed up localhost loading
if self.path.endswith(('.js', '.wasm', '.css', '.woff2', '.png', '.jpg', '.svg')):
self.send_header('Cache-Control', 'public, max-age=3600')
super().end_headers() super().end_headers()
def do_OPTIONS(self): def do_OPTIONS(self):
...@@ -83,6 +134,50 @@ class CORSRequestHandler(http.server.SimpleHTTPRequestHandler): ...@@ -83,6 +134,50 @@ class CORSRequestHandler(http.server.SimpleHTTPRequestHandler):
path = os.path.join(path, 'index.html') path = os.path.join(path, 'index.html')
if not os.path.exists(path): if not os.path.exists(path):
self.path = '/index.html' self.path = '/index.html'
path = self.translate_path(self.path)
# Check for compressed versions (gzip/brotli) to optimize for 4G throttling
accept_encoding = self.headers.get('Accept-Encoding', '')
compressed_path = None
encoding = None
if os.path.exists(path):
# Prefer brotli, then gzip
if 'br' in accept_encoding and os.path.exists(path + '.br'):
compressed_path = path + '.br'
encoding = 'br'
elif 'gzip' in accept_encoding and os.path.exists(path + '.gz'):
compressed_path = path + '.gz'
encoding = 'gzip'
if compressed_path and encoding:
# Serve compressed file
try:
with open(compressed_path, 'rb') as f:
content = f.read()
self.send_response(200)
# Set content type based on original file
if path.endswith('.wasm'):
self.send_header('Content-Type', 'application/wasm')
elif path.endswith('.js'):
self.send_header('Content-Type', 'application/javascript')
elif path.endswith('.css'):
self.send_header('Content-Type', 'text/css')
elif path.endswith('.json'):
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Encoding', encoding)
self.send_header('Content-Length', str(len(content)))
self.send_header('Vary', 'Accept-Encoding')
self.end_headers()
self.wfile.write(content)
return
except Exception as e:
print(f"⚠️ Error serving compressed file: {e}")
# Fall through to serve uncompressed
# Serve uncompressed file
return super().do_GET() return super().do_GET()
def log_message(self, fmt, *args): def log_message(self, fmt, *args):
...@@ -121,6 +216,7 @@ kill_port "${PORT}" ...@@ -121,6 +216,7 @@ kill_port "${PORT}"
set_env_dev set_env_dev
build_web build_web
copy_x_app_sdk copy_x_app_sdk
compress_assets_for_dev
start_server start_server
trap cleanup EXIT INT TERM trap cleanup EXIT INT TERM
sleep 2 sleep 2
......
...@@ -81,7 +81,13 @@ echo "" ...@@ -81,7 +81,13 @@ echo ""
# Step 1: Build and export # Step 1: Build and export
echo "📦 Step 1: Building and exporting..." echo "📦 Step 1: Building and exporting..."
./export_dev.sh # Pass through KEEP_CANVASKIT environment variable if set
if [ -n "${KEEP_CANVASKIT:-}" ]; then
echo " ℹ️ KEEP_CANVASKIT=${KEEP_CANVASKIT} will be passed to export_dev.sh"
KEEP_CANVASKIT="${KEEP_CANVASKIT}" ./export_dev.sh
else
./export_dev.sh
fi
echo "" echo ""
update_nginx_config update_nginx_config
......
...@@ -42,10 +42,6 @@ ...@@ -42,10 +42,6 @@
#app-loader { #app-loader {
position: fixed; position: fixed;
inset: 0; inset: 0;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background: #ffffff url('assets/assets/images/splash_screen.webp') center/cover no-repeat; background: #ffffff url('assets/assets/images/splash_screen.webp') center/cover no-repeat;
z-index: 9999; z-index: 9999;
transition: opacity 0.3s ease, visibility 0.3s ease; transition: opacity 0.3s ease, visibility 0.3s ease;
...@@ -66,35 +62,6 @@ ...@@ -66,35 +62,6 @@
visibility: hidden; visibility: hidden;
pointer-events: none; pointer-events: none;
} }
.app-loader__spinner {
width: 56px;
height: 56px;
border: 6px solid rgba(0, 0, 0, 0.1);
border-top-color: #2f80ed;
border-radius: 50%;
animation: app-loader-spin 1s linear infinite;
position: relative;
z-index: 1;
}
.app-loader__text {
margin-top: 20px;
font-family: 'Helvetica Neue', Arial, sans-serif;
font-size: 15px;
color: #3b4a6b;
letter-spacing: 0.2px;
text-align: center;
max-width: 240px;
position: relative;
z-index: 1;
}
@keyframes app-loader-spin {
to {
transform: rotate(360deg);
}
}
</style> </style>
<!-- x-app-sdk integration --> <!-- x-app-sdk integration -->
...@@ -309,10 +276,7 @@ ...@@ -309,10 +276,7 @@
</script> </script>
</head> </head>
<body> <body>
<div id="app-loader" role="status" aria-live="polite" aria-label="Đang khởi động ứng dụng"> <div id="app-loader" aria-hidden="true"></div>
<div class="app-loader__spinner"></div>
<div class="app-loader__text">Đang khởi động MyPoint...</div>
</div>
<script src="flutter_bootstrap.js" async></script> <script src="flutter_bootstrap.js" async></script>
<script> <script>
...@@ -332,7 +296,6 @@ ...@@ -332,7 +296,6 @@
} }
window.addEventListener('flutter-first-frame', hideLoader); window.addEventListener('flutter-first-frame', hideLoader);
// Fallback: ensure loader disappears even if event never fires
window.addEventListener('load', function () { window.addEventListener('load', function () {
setTimeout(hideLoader, 4000); setTimeout(hideLoader, 4000);
}); });
......
Supports Markdown
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