Skip to content

Instantly share code, notes, and snippets.

@zs-dima
Last active February 27, 2023 08:52
Show Gist options
  • Save zs-dima/5e7de75991fc707353afda970686dbba to your computer and use it in GitHub Desktop.
Save zs-dima/5e7de75991fc707353afda970686dbba to your computer and use it in GitHub Desktop.
Runtime environment variables for Flutter Web + Update Flutter Web app JS as soon as app version updated
FROM dart:stable AS build_dart
WORKDIR /app
COPY ./tool/ ./tool/
RUN dart compile exe tool/web_env.dart -o tool/web-env
FROM plugfox/flutter:stable-web AS build_web
WORKDIR /home
# Copy app source code and compile it
COPY --chown=101:101 . .
# Ensure packages are still up-to-date if anything has changed
RUN flutter pub get
RUN flutter pub run build_runner build --delete-conflicting-outputs --release
RUN flutter pub global run intl_utils:generate
RUN flutter build web --release --no-source-maps \
--pwa-strategy offline-first \
--web-renderer canvaskit --dart-define=FLUTTER_WEB_USE_SKIA=true
FROM nginx:alpine as production
COPY --from=build_dart /runtime/ /
COPY --from=build_dart /app/tool/web-env /app/bin/
COPY --from=build_dart /app/tool/entrypoint.sh /app/bin/
COPY --from=build_web --chown=101:101 /home/build/web /usr/share/nginx/html
COPY deploy/nginx/mime.types /etc/nginx/mime.types
COPY deploy/nginx/nginx.conf /etc/nginx/nginx.conf
RUN chmod +x /app/bin/entrypoint.sh
# Add lables
LABEL name="..." \
vcs-url="https://github.com/..." \
github="https://github.com/..." \
maintainer="..." \
authors="..." \
family=""
# Start server
EXPOSE 80/tcp
ENTRYPOINT ["/app/bin/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
#!/bin/sh
/app/bin/web-env environment=$APP_ENVIRONMENT version=$APP_VERSION api_host=$APP_API_HOST api_port=$APP_API_PORT api_ssl=$APP_API_SSL
# This will exec the CMD from your Dockerfile
exec "$@"
import 'dart:collection' show ListBase;
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
void main(List<String> arguments) async {
const versionVarName = 'app_version';
const webRoot = '/usr/share/nginx/html';
const envFileName = 'environment.json';
const envJsonPath = '$webRoot/assets/asset/$envFileName';
const indexHtmlPath = '$webRoot/index.html';
const flutterJsPath = '$webRoot/flutter.js';
const workerJsPath = '$webRoot/flutter_service_worker.js';
const versionJsonPath = '$webRoot/version.json';
var appVersion = '';
final envJson = <String, Object>{};
for (final argument in arguments) {
if (argument.contains('=')) {
final split = argument.split('=');
final arg = split.first.toLowerCase();
final value = split.last;
if (arg.isEmpty || value.isEmpty) {
continue;
}
if (int.tryParse(value) != null) {
envJson[arg] = int.parse(value);
} else if (double.tryParse(value) != null) {
envJson[arg] = double.parse(value);
} else if (value.toLowerCase() == 'true') {
envJson[arg] = true;
} else if (value.toLowerCase() == 'false') {
envJson[arg] = false;
} else {
envJson[arg] = value;
}
if (arg == versionVarName) {
appVersion = value;
}
}
}
if (appVersion.isEmpty) {
final versionJsonFile = File(versionJsonPath);
final versionJson = jsonDecode(await versionJsonFile.readAsString());
appVersion = '${versionJson['version']}+${versionJson['build_number']}'.trim();
}
final envFile = File(envJsonPath);
final envFileContent = const JsonEncoder.withIndent(' ').convert(envJson);
final envFileMd5 = md5.convert(utf8.encode(envFileContent)).toString();
await envFile.writeAsString(envFileContent);
final flutterJsFile = File(flutterJsPath);
final flutterJsContent = await flutterJsFile.readAsString();
final flutterJsReplaced = flutterJsContent
.replaceAll('"main.dart.js"', '"main.dart.js?$appVersion"')
.replaceAll('"flutter_service_worker.js?v=', '"flutter_service_worker.js?v=$appVersion');
if (flutterJsContent != flutterJsReplaced) {
await flutterJsFile.writeAsString(flutterJsReplaced);
}
final indexHtmlFile = File(indexHtmlPath);
final indexHtml = await indexHtmlFile.readAsString();
final indexHtmlReplaced = indexHtml.replaceAll('"flutter.js"', '"flutter.js?$appVersion"');
if (indexHtml != indexHtmlReplaced) {
await indexHtmlFile.writeAsString(indexHtmlReplaced);
}
final workerJsFile = File(workerJsPath);
final workerJsContent = await workerJsFile.readAsString();
final workerJsReplaced = workerJsContent.replaceAll(
RegExp('"assets/asset/$envFileName": "[0-9a-f]{32}"'),
'"assets/asset/$envFileName": "$envFileMd5"',
);
if (workerJsContent != workerJsReplaced) {
await workerJsFile.writeAsString(workerJsReplaced);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment