mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[tool] Refactor WebTemplate to be immutable (#168201)
This flows better. No uncertainty about calling the substitutions function more than once, etc.
This commit is contained in:
parent
429d7e886a
commit
d80c390dc3
@ -571,7 +571,7 @@ _flutter.buildConfig = ${jsonEncode(buildConfig)};
|
|||||||
// in question.
|
// in question.
|
||||||
final String? serviceWorkerVersion =
|
final String? serviceWorkerVersion =
|
||||||
includeServiceWorkerSettings ? Random().nextInt(1 << 32).toString() : null;
|
includeServiceWorkerSettings ? Random().nextInt(1 << 32).toString() : null;
|
||||||
bootstrapTemplate.applySubstitutions(
|
final String bootstrapContent = bootstrapTemplate.withSubstitutions(
|
||||||
baseHref: '',
|
baseHref: '',
|
||||||
serviceWorkerVersion: serviceWorkerVersion,
|
serviceWorkerVersion: serviceWorkerVersion,
|
||||||
flutterJsFile: flutterJsFile,
|
flutterJsFile: flutterJsFile,
|
||||||
@ -581,7 +581,7 @@ _flutter.buildConfig = ${jsonEncode(buildConfig)};
|
|||||||
final File outputFlutterBootstrapJs = fileSystem.file(
|
final File outputFlutterBootstrapJs = fileSystem.file(
|
||||||
fileSystem.path.join(environment.outputDir.path, 'flutter_bootstrap.js'),
|
fileSystem.path.join(environment.outputDir.path, 'flutter_bootstrap.js'),
|
||||||
);
|
);
|
||||||
await outputFlutterBootstrapJs.writeAsString(bootstrapTemplate.content);
|
await outputFlutterBootstrapJs.writeAsString(bootstrapContent);
|
||||||
|
|
||||||
await for (final FileSystemEntity file in webResources.list(recursive: true)) {
|
await for (final FileSystemEntity file in webResources.list(recursive: true)) {
|
||||||
if (file is File && file.basename == 'index.html') {
|
if (file is File && file.basename == 'index.html') {
|
||||||
@ -592,18 +592,18 @@ _flutter.buildConfig = ${jsonEncode(buildConfig)};
|
|||||||
_emitWebTemplateWarning(environment, relativePath, warning);
|
_emitWebTemplateWarning(environment, relativePath, warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
indexHtmlTemplate.applySubstitutions(
|
final String indexHtmlContent = indexHtmlTemplate.withSubstitutions(
|
||||||
baseHref: environment.defines[kBaseHref] ?? '/',
|
baseHref: environment.defines[kBaseHref] ?? '/',
|
||||||
serviceWorkerVersion: serviceWorkerVersion,
|
serviceWorkerVersion: serviceWorkerVersion,
|
||||||
flutterJsFile: flutterJsFile,
|
flutterJsFile: flutterJsFile,
|
||||||
buildConfig: buildConfig,
|
buildConfig: buildConfig,
|
||||||
flutterBootstrapJs: bootstrapTemplate.content,
|
flutterBootstrapJs: bootstrapContent,
|
||||||
);
|
);
|
||||||
final File outputIndexHtml = fileSystem.file(
|
final File outputIndexHtml = fileSystem.file(
|
||||||
fileSystem.path.join(environment.outputDir.path, relativePath),
|
fileSystem.path.join(environment.outputDir.path, relativePath),
|
||||||
);
|
);
|
||||||
await outputIndexHtml.create(recursive: true);
|
await outputIndexHtml.create(recursive: true);
|
||||||
await outputIndexHtml.writeAsString(indexHtmlTemplate.content);
|
await outputIndexHtml.writeAsString(indexHtmlContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ class WebAssetServer implements AssetReader {
|
|||||||
this._canaryFeatures, {
|
this._canaryFeatures, {
|
||||||
required this.webRenderer,
|
required this.webRenderer,
|
||||||
required this.useLocalCanvasKit,
|
required this.useLocalCanvasKit,
|
||||||
}) : basePath = _getWebTemplate('index.html', _kDefaultIndex).getBaseHref() {
|
}) : basePath = WebTemplate.baseHref(_htmlTemplate('index.html', _kDefaultIndex)) {
|
||||||
// TODO(srujzs): Remove this assertion when the library bundle format is
|
// TODO(srujzs): Remove this assertion when the library bundle format is
|
||||||
// supported without canary mode.
|
// supported without canary mode.
|
||||||
if (_ddcModuleSystem) {
|
if (_ddcModuleSystem) {
|
||||||
@ -671,13 +671,12 @@ _flutter.buildConfig = ${jsonEncode(buildConfig)};
|
|||||||
'flutter_bootstrap.js',
|
'flutter_bootstrap.js',
|
||||||
generateDefaultFlutterBootstrapScript(includeServiceWorkerSettings: false),
|
generateDefaultFlutterBootstrapScript(includeServiceWorkerSettings: false),
|
||||||
);
|
);
|
||||||
bootstrapTemplate.applySubstitutions(
|
return bootstrapTemplate.withSubstitutions(
|
||||||
baseHref: '/',
|
baseHref: '/',
|
||||||
serviceWorkerVersion: null,
|
serviceWorkerVersion: null,
|
||||||
buildConfig: _buildConfigString,
|
buildConfig: _buildConfigString,
|
||||||
flutterJsFile: _flutterJsFile,
|
flutterJsFile: _flutterJsFile,
|
||||||
);
|
);
|
||||||
return bootstrapTemplate.content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shelf.Response _serveFlutterBootstrapJs() {
|
shelf.Response _serveFlutterBootstrapJs() {
|
||||||
@ -689,16 +688,15 @@ _flutter.buildConfig = ${jsonEncode(buildConfig)};
|
|||||||
|
|
||||||
shelf.Response _serveIndexHtml() {
|
shelf.Response _serveIndexHtml() {
|
||||||
final WebTemplate indexHtml = _getWebTemplate('index.html', _kDefaultIndex);
|
final WebTemplate indexHtml = _getWebTemplate('index.html', _kDefaultIndex);
|
||||||
indexHtml.applySubstitutions(
|
|
||||||
// Currently, we don't support --base-href for the "run" command.
|
|
||||||
baseHref: '/',
|
|
||||||
serviceWorkerVersion: null,
|
|
||||||
buildConfig: _buildConfigString,
|
|
||||||
flutterJsFile: _flutterJsFile,
|
|
||||||
flutterBootstrapJs: _flutterBootstrapJsContent,
|
|
||||||
);
|
|
||||||
return shelf.Response.ok(
|
return shelf.Response.ok(
|
||||||
indexHtml.content,
|
indexHtml.withSubstitutions(
|
||||||
|
// Currently, we don't support --base-href for the "run" command.
|
||||||
|
baseHref: '/',
|
||||||
|
serviceWorkerVersion: null,
|
||||||
|
buildConfig: _buildConfigString,
|
||||||
|
flutterJsFile: _flutterJsFile,
|
||||||
|
flutterBootstrapJs: _flutterBootstrapJsContent,
|
||||||
|
),
|
||||||
headers: <String, String>{HttpHeaders.contentTypeHeader: 'text/html'},
|
headers: <String, String>{HttpHeaders.contentTypeHeader: 'text/html'},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1375,7 +1373,11 @@ String? _stripBasePath(String path, String basePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
WebTemplate _getWebTemplate(String filename, String fallbackContent) {
|
WebTemplate _getWebTemplate(String filename, String fallbackContent) {
|
||||||
final File template = globals.fs.currentDirectory.childDirectory('web').childFile(filename);
|
final String htmlContent = _htmlTemplate(filename, fallbackContent);
|
||||||
final String htmlContent = template.existsSync() ? template.readAsStringSync() : fallbackContent;
|
|
||||||
return WebTemplate(htmlContent);
|
return WebTemplate(htmlContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _htmlTemplate(String filename, String fallbackContent) {
|
||||||
|
final File template = globals.fs.currentDirectory.childDirectory('web').childFile(filename);
|
||||||
|
return template.existsSync() ? template.readAsStringSync() : fallbackContent;
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import 'package:html/dom.dart';
|
import 'package:html/dom.dart';
|
||||||
import 'package:html/parser.dart';
|
import 'package:html/parser.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import 'base/common.dart';
|
import 'base/common.dart';
|
||||||
import 'base/file_system.dart';
|
import 'base/file_system.dart';
|
||||||
@ -29,16 +30,12 @@ class WebTemplateWarning {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
class WebTemplate {
|
class WebTemplate {
|
||||||
WebTemplate(this._content);
|
const WebTemplate(this._content);
|
||||||
|
|
||||||
String get content => _content;
|
final String _content;
|
||||||
String _content;
|
|
||||||
|
|
||||||
Document _getDocument() => parse(_content);
|
static String baseHref(String html) {
|
||||||
|
final Element? baseElement = parse(html).querySelector('base');
|
||||||
/// Parses the base href from the index.html file.
|
|
||||||
String getBaseHref() {
|
|
||||||
final Element? baseElement = _getDocument().querySelector('base');
|
|
||||||
final String? baseHref =
|
final String? baseHref =
|
||||||
baseElement?.attributes == null ? null : baseElement!.attributes['href'];
|
baseElement?.attributes == null ? null : baseElement!.attributes['href'];
|
||||||
|
|
||||||
@ -94,20 +91,23 @@ class WebTemplate {
|
|||||||
return WebTemplateWarning(warningText, lineCount + 1);
|
return WebTemplateWarning(warningText, lineCount + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies substitutions to the content of the index.html file.
|
/// Applies substitutions to the content of the index.html file and returns the result.
|
||||||
void applySubstitutions({
|
@useResult
|
||||||
|
String withSubstitutions({
|
||||||
required String baseHref,
|
required String baseHref,
|
||||||
required String? serviceWorkerVersion,
|
required String? serviceWorkerVersion,
|
||||||
required File flutterJsFile,
|
required File flutterJsFile,
|
||||||
String? buildConfig,
|
String? buildConfig,
|
||||||
String? flutterBootstrapJs,
|
String? flutterBootstrapJs,
|
||||||
}) {
|
}) {
|
||||||
if (_content.contains(kBaseHrefPlaceholder)) {
|
String newContent = _content;
|
||||||
_content = _content.replaceAll(kBaseHrefPlaceholder, baseHref);
|
|
||||||
|
if (newContent.contains(kBaseHrefPlaceholder)) {
|
||||||
|
newContent = newContent.replaceAll(kBaseHrefPlaceholder, baseHref);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceWorkerVersion != null) {
|
if (serviceWorkerVersion != null) {
|
||||||
_content = _content
|
newContent = newContent
|
||||||
.replaceFirst(
|
.replaceFirst(
|
||||||
// Support older `var` syntax as well as new `const` syntax
|
// Support older `var` syntax as well as new `const` syntax
|
||||||
RegExp('(const|var) serviceWorkerVersion = null'),
|
RegExp('(const|var) serviceWorkerVersion = null'),
|
||||||
@ -120,21 +120,22 @@ class WebTemplate {
|
|||||||
"navigator.serviceWorker.register('flutter_service_worker.js?v=$serviceWorkerVersion')",
|
"navigator.serviceWorker.register('flutter_service_worker.js?v=$serviceWorkerVersion')",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_content = _content.replaceAll(
|
newContent = newContent.replaceAll(
|
||||||
'{{flutter_service_worker_version}}',
|
'{{flutter_service_worker_version}}',
|
||||||
serviceWorkerVersion != null ? '"$serviceWorkerVersion"' : 'null',
|
serviceWorkerVersion != null ? '"$serviceWorkerVersion"' : 'null',
|
||||||
);
|
);
|
||||||
if (buildConfig != null) {
|
if (buildConfig != null) {
|
||||||
_content = _content.replaceAll('{{flutter_build_config}}', buildConfig);
|
newContent = newContent.replaceAll('{{flutter_build_config}}', buildConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_content.contains('{{flutter_js}}')) {
|
if (newContent.contains('{{flutter_js}}')) {
|
||||||
_content = _content.replaceAll('{{flutter_js}}', flutterJsFile.readAsStringSync());
|
newContent = newContent.replaceAll('{{flutter_js}}', flutterJsFile.readAsStringSync());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flutterBootstrapJs != null) {
|
if (flutterBootstrapJs != null) {
|
||||||
_content = _content.replaceAll('{{flutter_bootstrap_js}}', flutterBootstrapJs);
|
newContent = newContent.replaceAll('{{flutter_bootstrap_js}}', flutterBootstrapJs);
|
||||||
}
|
}
|
||||||
|
return newContent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,93 +221,96 @@ void main() {
|
|||||||
flutterJs.writeAsStringSync('(flutter.js content)');
|
flutterJs.writeAsStringSync('(flutter.js content)');
|
||||||
|
|
||||||
test('can parse baseHref', () {
|
test('can parse baseHref', () {
|
||||||
expect(WebTemplate('<base href="/foo/111/">').getBaseHref(), 'foo/111');
|
expect(WebTemplate.baseHref('<base href="/foo/111/">'), 'foo/111');
|
||||||
expect(WebTemplate(htmlSample1).getBaseHref(), 'foo/222');
|
expect(WebTemplate.baseHref(htmlSample1), 'foo/222');
|
||||||
expect(WebTemplate(htmlSample2).getBaseHref(), ''); // Placeholder base href.
|
expect(WebTemplate.baseHref(htmlSample2), ''); // Placeholder base href.
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles missing baseHref', () {
|
test('handles missing baseHref', () {
|
||||||
expect(WebTemplate('').getBaseHref(), '');
|
expect(WebTemplate.baseHref(''), '');
|
||||||
expect(WebTemplate('<base>').getBaseHref(), '');
|
expect(WebTemplate.baseHref('<base>'), '');
|
||||||
expect(WebTemplate(htmlSample3).getBaseHref(), '');
|
expect(WebTemplate.baseHref(htmlSample3), '');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('throws on invalid baseHref', () {
|
test('throws on invalid baseHref', () {
|
||||||
expect(() => WebTemplate('<base href>').getBaseHref(), throwsToolExit());
|
expect(() => WebTemplate.baseHref('<base href>'), throwsToolExit());
|
||||||
expect(() => WebTemplate('<base href="">').getBaseHref(), throwsToolExit());
|
expect(() => WebTemplate.baseHref('<base href="">'), throwsToolExit());
|
||||||
expect(() => WebTemplate('<base href="foo/111">').getBaseHref(), throwsToolExit());
|
expect(() => WebTemplate.baseHref('<base href="foo/111">'), throwsToolExit());
|
||||||
expect(() => WebTemplate('<base href="foo/111/">').getBaseHref(), throwsToolExit());
|
expect(() => WebTemplate.baseHref('<base href="foo/111/">'), throwsToolExit());
|
||||||
expect(() => WebTemplate('<base href="/foo/111">').getBaseHref(), throwsToolExit());
|
expect(() => WebTemplate.baseHref('<base href="/foo/111">'), throwsToolExit());
|
||||||
});
|
});
|
||||||
|
|
||||||
test('applies substitutions', () {
|
test('applies substitutions', () {
|
||||||
final WebTemplate indexHtml = WebTemplate(htmlSample2);
|
const WebTemplate indexHtml = WebTemplate(htmlSample2);
|
||||||
indexHtml.applySubstitutions(
|
|
||||||
baseHref: '/foo/333/',
|
|
||||||
serviceWorkerVersion: 'v123xyz',
|
|
||||||
flutterJsFile: flutterJs,
|
|
||||||
);
|
|
||||||
expect(
|
expect(
|
||||||
indexHtml.content,
|
indexHtml.withSubstitutions(
|
||||||
|
baseHref: '/foo/333/',
|
||||||
|
serviceWorkerVersion: 'v123xyz',
|
||||||
|
flutterJsFile: flutterJs,
|
||||||
|
),
|
||||||
htmlSample2Replaced(baseHref: '/foo/333/', serviceWorkerVersion: 'v123xyz'),
|
htmlSample2Replaced(baseHref: '/foo/333/', serviceWorkerVersion: 'v123xyz'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('applies substitutions with legacy var version syntax', () {
|
test('applies substitutions with legacy var version syntax', () {
|
||||||
final WebTemplate indexHtml = WebTemplate(htmlSampleLegacyVar);
|
const WebTemplate indexHtml = WebTemplate(htmlSampleLegacyVar);
|
||||||
indexHtml.applySubstitutions(
|
|
||||||
baseHref: '/foo/333/',
|
|
||||||
serviceWorkerVersion: 'v123xyz',
|
|
||||||
flutterJsFile: flutterJs,
|
|
||||||
);
|
|
||||||
expect(
|
expect(
|
||||||
indexHtml.content,
|
indexHtml.withSubstitutions(
|
||||||
|
baseHref: '/foo/333/',
|
||||||
|
serviceWorkerVersion: 'v123xyz',
|
||||||
|
flutterJsFile: flutterJs,
|
||||||
|
),
|
||||||
htmlSample2Replaced(baseHref: '/foo/333/', serviceWorkerVersion: 'v123xyz'),
|
htmlSample2Replaced(baseHref: '/foo/333/', serviceWorkerVersion: 'v123xyz'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('applies substitutions to inline flutter.js bootstrap script', () {
|
test('applies substitutions to inline flutter.js bootstrap script', () {
|
||||||
final WebTemplate indexHtml = WebTemplate(htmlSampleInlineFlutterJsBootstrap);
|
const WebTemplate indexHtml = WebTemplate(htmlSampleInlineFlutterJsBootstrap);
|
||||||
expect(indexHtml.getWarnings(), isEmpty);
|
expect(indexHtml.getWarnings(), isEmpty);
|
||||||
|
|
||||||
indexHtml.applySubstitutions(
|
expect(
|
||||||
baseHref: '/',
|
indexHtml.withSubstitutions(
|
||||||
serviceWorkerVersion: '(service worker version)',
|
baseHref: '/',
|
||||||
flutterJsFile: flutterJs,
|
serviceWorkerVersion: '(service worker version)',
|
||||||
buildConfig: '(build config)',
|
flutterJsFile: flutterJs,
|
||||||
|
buildConfig: '(build config)',
|
||||||
|
),
|
||||||
|
htmlSampleInlineFlutterJsBootstrapOutput,
|
||||||
);
|
);
|
||||||
expect(indexHtml.content, htmlSampleInlineFlutterJsBootstrapOutput);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('applies substitutions to full flutter_bootstrap.js replacement', () {
|
test('applies substitutions to full flutter_bootstrap.js replacement', () {
|
||||||
final WebTemplate indexHtml = WebTemplate(htmlSampleFullFlutterBootstrapReplacement);
|
const WebTemplate indexHtml = WebTemplate(htmlSampleFullFlutterBootstrapReplacement);
|
||||||
expect(indexHtml.getWarnings(), isEmpty);
|
expect(indexHtml.getWarnings(), isEmpty);
|
||||||
|
|
||||||
indexHtml.applySubstitutions(
|
expect(
|
||||||
baseHref: '/',
|
indexHtml.withSubstitutions(
|
||||||
serviceWorkerVersion: '(service worker version)',
|
baseHref: '/',
|
||||||
flutterJsFile: flutterJs,
|
serviceWorkerVersion: '(service worker version)',
|
||||||
buildConfig: '(build config)',
|
flutterJsFile: flutterJs,
|
||||||
flutterBootstrapJs: '(flutter bootstrap script)',
|
buildConfig: '(build config)',
|
||||||
|
flutterBootstrapJs: '(flutter bootstrap script)',
|
||||||
|
),
|
||||||
|
htmlSampleFullFlutterBootstrapReplacementOutput,
|
||||||
);
|
);
|
||||||
expect(indexHtml.content, htmlSampleFullFlutterBootstrapReplacementOutput);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('re-parses after substitutions', () {
|
test('re-parses after substitutions', () {
|
||||||
final WebTemplate indexHtml = WebTemplate(htmlSample2);
|
const WebTemplate indexHtml = WebTemplate(htmlSample2);
|
||||||
expect(indexHtml.getBaseHref(), ''); // Placeholder base href.
|
expect(WebTemplate.baseHref(htmlSample2), ''); // Placeholder base href.
|
||||||
|
|
||||||
indexHtml.applySubstitutions(
|
final String substituted = indexHtml.withSubstitutions(
|
||||||
baseHref: '/foo/333/',
|
baseHref: '/foo/333/',
|
||||||
serviceWorkerVersion: 'v123xyz',
|
serviceWorkerVersion: 'v123xyz',
|
||||||
flutterJsFile: flutterJs,
|
flutterJsFile: flutterJs,
|
||||||
);
|
);
|
||||||
// The parsed base href should be updated after substitutions.
|
// The parsed base href should be updated after substitutions.
|
||||||
expect(indexHtml.getBaseHref(), 'foo/333');
|
expect(WebTemplate.baseHref(substituted), 'foo/333');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('warns on legacy service worker patterns', () {
|
test('warns on legacy service worker patterns', () {
|
||||||
final WebTemplate indexHtml = WebTemplate(htmlSampleLegacyVar);
|
const WebTemplate indexHtml = WebTemplate(htmlSampleLegacyVar);
|
||||||
final List<WebTemplateWarning> warnings = indexHtml.getWarnings();
|
final List<WebTemplateWarning> warnings = indexHtml.getWarnings();
|
||||||
expect(warnings.length, 2);
|
expect(warnings.length, 2);
|
||||||
|
|
||||||
@ -316,7 +319,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('warns on legacy FlutterLoader.loadEntrypoint', () {
|
test('warns on legacy FlutterLoader.loadEntrypoint', () {
|
||||||
final WebTemplate indexHtml = WebTemplate(htmlSampleLegacyLoadEntrypoint);
|
const WebTemplate indexHtml = WebTemplate(htmlSampleLegacyLoadEntrypoint);
|
||||||
final List<WebTemplateWarning> warnings = indexHtml.getWarnings();
|
final List<WebTemplateWarning> warnings = indexHtml.getWarnings();
|
||||||
|
|
||||||
expect(warnings.length, 1);
|
expect(warnings.length, 1);
|
||||||
|
Loading…
Reference in New Issue
Block a user