Unverified Commit 4a33813b authored by Balvinder Singh Gambhir's avatar Balvinder Singh Gambhir Committed by GitHub

[flutter_tools] added base-href command in web (#80519)

parent 447b7f1d
......@@ -35,6 +35,12 @@ const String kDart2jsOptimization = 'Dart2jsOptimization';
/// Whether to disable dynamic generation code to satisfy csp policies.
const String kCspMode = 'cspMode';
/// Base href to set in index.html in flutter build command
const String kBaseHref = 'baseHref';
/// Placeholder for base href
const String kBaseHrefPlaceholder = r'$FLUTTER_BASE_HREF';
/// The caching strategy to use for service worker generation.
const String kServiceWorkerStrategy = 'ServiceWorkerStrategy';
......@@ -356,7 +362,7 @@ class WebReleaseBundle extends Target {
// in question.
if (environment.fileSystem.path.basename(inputFile.path) == 'index.html') {
final String randomHash = Random().nextInt(4294967296).toString();
final String resultString = inputFile.readAsStringSync()
String resultString = inputFile.readAsStringSync()
.replaceFirst(
'var serviceWorkerVersion = null',
"var serviceWorkerVersion = '$randomHash'",
......@@ -367,6 +373,13 @@ class WebReleaseBundle extends Target {
"navigator.serviceWorker.register('flutter_service_worker.js')",
"navigator.serviceWorker.register('flutter_service_worker.js?v=$randomHash')",
);
if (resultString.contains(kBaseHrefPlaceholder) &&
environment.defines[kBaseHref] == null) {
resultString = resultString.replaceAll(kBaseHrefPlaceholder, '/');
} else if (resultString.contains(kBaseHrefPlaceholder) &&
environment.defines[kBaseHref] != null) {
resultString = resultString.replaceAll(kBaseHrefPlaceholder, environment.defines[kBaseHref]);
}
outputFile.writeAsStringSync(resultString);
continue;
}
......
......@@ -10,6 +10,7 @@ import '../base/common.dart';
import '../build_info.dart';
import '../build_system/targets/web.dart';
import '../features.dart';
import '../globals.dart' as globals;
import '../project.dart';
import '../runner/flutter_command.dart'
show DevelopmentArtifact, FlutterCommandResult;
......@@ -42,6 +43,7 @@ class BuildWebCommand extends BuildSubCommand {
'to view and debug the original source code of a compiled and minified Dart '
'application.'
);
argParser.addOption('pwa-strategy',
defaultsTo: kOfflineFirst,
help: 'The caching strategy to be used by the PWA service worker.',
......@@ -59,6 +61,13 @@ class BuildWebCommand extends BuildSubCommand {
'is not desirable',
},
);
argParser.addOption('base-href',
help: 'Overrides the href attribute of the <base> tag in web/index.html. '
'No change is done to web/index.html file if this flag is not provided. '
'The value has to start and end with a slash "/". '
'For more information: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base'
);
}
@override
......@@ -87,6 +96,20 @@ class BuildWebCommand extends BuildSubCommand {
if (buildInfo.isDebug) {
throwToolExit('debug builds cannot be built directly for the web. Try using "flutter run"');
}
if (stringArg('base-href') != null && !(stringArg('base-href').startsWith('/') && stringArg('base-href').endsWith('/'))) {
throwToolExit('base-href should start and end with /');
}
if (!globals.fs.currentDirectory
.childDirectory('web')
.childFile('index.html')
.readAsStringSync()
.contains(kBaseHrefPlaceholder) &&
stringArg('base-href') != null) {
throwToolExit(
"Couldn't find the placeholder for base href. "
r'Please add `<base href="$FLUTTER_BASE_HREF">` to web/index.html'
);
}
displayNullSafetyMode(buildInfo);
await buildWeb(
flutterProject,
......@@ -96,6 +119,7 @@ class BuildWebCommand extends BuildSubCommand {
stringArg('pwa-strategy'),
boolArg('source-maps'),
boolArg('native-null-assertions'),
stringArg('base-href'),
);
return FlutterCommandResult.success();
}
......
......@@ -28,6 +28,7 @@ import '../base/logger.dart';
import '../base/net.dart';
import '../base/platform.dart';
import '../build_info.dart';
import '../build_system/targets/web.dart';
import '../bundle.dart';
import '../cache.dart';
import '../compile.dart';
......@@ -488,6 +489,12 @@ class WebAssetServer implements AssetReader {
.childFile('index.html');
if (indexFile.existsSync()) {
String indexFileContent = indexFile.readAsStringSync();
if (indexFileContent.contains(kBaseHrefPlaceholder)) {
indexFileContent = indexFileContent.replaceAll(kBaseHrefPlaceholder, '/');
headers[HttpHeaders.contentLengthHeader] = indexFileContent.length.toString();
return shelf.Response.ok(indexFileContent,headers: headers);
}
headers[HttpHeaders.contentLengthHeader] =
indexFile.lengthSync().toString();
return shelf.Response.ok(indexFile.openRead(), headers: headers);
......@@ -1025,13 +1032,12 @@ String _stripTrailingSlashes(String path) {
String _parseBasePathFromIndexHtml(File indexHtml) {
final String htmlContent =
indexHtml.existsSync() ? indexHtml.readAsStringSync() : _kDefaultIndex;
final Document document = parse(htmlContent);
final Element baseElement = document.querySelector('base');
String baseHref =
baseElement?.attributes == null ? null : baseElement.attributes['href'];
if (baseHref == null) {
if (baseHref == null || baseHref == kBaseHrefPlaceholder) {
baseHref = '';
} else if (!baseHref.startsWith('/')) {
throw ToolExit(
......
......@@ -297,6 +297,7 @@ class ResidentWebRunner extends ResidentRunner {
kNoneWorker,
true,
debuggingOptions.nativeNullAssertions,
null,
);
}
await device.device.startApp(
......@@ -365,6 +366,7 @@ class ResidentWebRunner extends ResidentRunner {
kNoneWorker,
true,
debuggingOptions.nativeNullAssertions,
kBaseHref,
);
} on ToolExit {
return OperationResult(1, 'Failed to recompile application.');
......
......@@ -26,6 +26,7 @@ Future<void> buildWeb(
String serviceWorkerStrategy,
bool sourceMaps,
bool nativeNullAssertions,
String baseHref,
) async {
if (!flutterProject.web.existsSync()) {
throwToolExit('Missing index.html.');
......@@ -49,6 +50,7 @@ Future<void> buildWeb(
kTargetFile: target,
kHasWebPlugins: hasWebPlugins.toString(),
kCspMode: csp.toString(),
kBaseHref : baseHref,
kSourceMapsEnabled: sourceMaps.toString(),
kNativeNullAssertions: nativeNullAssertions.toString(),
if (serviceWorkerStrategy != null)
......
......@@ -10,8 +10,11 @@
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="/">
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
......
......@@ -60,6 +60,7 @@ void main() {
null,
true,
true,
null,
), throwsToolExit());
}, overrides: <Type, Generator>{
Platform: () => fakePlatform,
......@@ -119,6 +120,7 @@ void main() {
'DartObfuscation': 'false',
'TrackWidgetCreation': 'false',
'TreeShakeIcons': 'false',
'baseHref': null,
});
}),
});
......
......@@ -101,6 +101,33 @@ void main() {
expect(environment.outputDir.childFile('version.json'), exists);
}));
test('Base href is created in index.html with given base-href after release build', () => testbed.run(() async {
environment.defines[kBuildMode] = 'release';
environment.defines[kBaseHref] = '/basehreftest/';
final Directory webResources = environment.projectDir.childDirectory('web');
webResources.childFile('index.html').createSync(recursive: true);
webResources.childFile('index.html').writeAsStringSync('''
<!DOCTYPE html><html><base href="$kBaseHrefPlaceholder"><head></head></html>
''');
environment.buildDir.childFile('main.dart.js').createSync();
await const WebReleaseBundle().build(environment);
expect(environment.outputDir.childFile('index.html').readAsStringSync(), contains('/basehreftest/'));
}));
test('null base href does not override existing base href in index.html', () => testbed.run(() async {
environment.defines[kBuildMode] = 'release';
final Directory webResources = environment.projectDir.childDirectory('web');
webResources.childFile('index.html').createSync(recursive: true);
webResources.childFile('index.html').writeAsStringSync('''
<!DOCTYPE html><html><head><base href='/basehreftest/'></head></html>
''');
environment.buildDir.childFile('main.dart.js').createSync();
await const WebReleaseBundle().build(environment);
expect(environment.outputDir.childFile('index.html').readAsStringSync(), contains('/basehreftest/'));
}));
test('WebReleaseBundle copies dart2js output and resource files to output directory', () => testbed.run(() async {
environment.defines[kBuildMode] = 'release';
final Directory webResources = environment.projectDir.childDirectory('web');
......
......@@ -10,6 +10,7 @@ import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/targets/web.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
......@@ -209,6 +210,19 @@ void main() {
expect(await response.readAsString(), htmlContent);
}));
test('serves index.html at / if href attribute is $kBaseHrefPlaceholder', () => testbed.run(() async {
const String htmlContent = '<html><head><base href ="$kBaseHrefPlaceholder"></head><body id="test"></body></html>';
final Directory webDir = globals.fs.currentDirectory.childDirectory('web')
..createSync();
webDir.childFile('index.html').writeAsStringSync(htmlContent);
final Response response = await webAssetServer
.handleRequest(Request('GET', Uri.parse('http://foobar/')));
expect(response.statusCode, HttpStatus.ok);
expect(await response.readAsString(), htmlContent.replaceAll(kBaseHrefPlaceholder, '/'));
}));
test('does not serve outside the base path', () => testbed.run(() async {
webAssetServer.basePath = 'base/path';
......
Markdown is supported
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