| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'package:html/dom.dart'; | |
| import 'package:html/parser.dart'; | |
| import 'base/common.dart'; | |
| /// Placeholder for base href | |
| const String kBaseHrefPlaceholder = r'$FLUTTER_BASE_HREF'; | |
| /// Utility class for parsing and performing operations on the contents of the | |
| /// index.html file. | |
| /// | |
| /// For example, to parse the base href from the index.html file: | |
| /// | |
| /// ```dart | |
| /// String parseBaseHref(File indexHtmlFile) { | |
| /// final IndexHtml indexHtml = IndexHtml(indexHtmlFile.readAsStringSync()); | |
| /// return indexHtml.getBaseHref(); | |
| /// } | |
| /// ``` | |
| class IndexHtml { | |
| IndexHtml(this._content); | |
| String get content => _content; | |
| String _content; | |
| Document _getDocument() => parse(_content); | |
| /// Parses the base href from the index.html file. | |
| String getBaseHref() { | |
| final Element? baseElement = _getDocument().querySelector('base'); | |
| final String? baseHref = baseElement?.attributes == null | |
| ? null | |
| : baseElement!.attributes['href']; | |
| if (baseHref == null || baseHref == kBaseHrefPlaceholder) { | |
| return ''; | |
| } | |
| if (!baseHref.startsWith('/')) { | |
| throw ToolExit( | |
| 'Error: The base href in "web/index.html" must be absolute (i.e. start ' | |
| 'with a "/"), but found: `${baseElement!.outerHtml}`.\n' | |
| '$_kBasePathExample', | |
| ); | |
| } | |
| if (!baseHref.endsWith('/')) { | |
| throw ToolExit( | |
| 'Error: The base href in "web/index.html" must end with a "/", but found: `${baseElement!.outerHtml}`.\n' | |
| '$_kBasePathExample', | |
| ); | |
| } | |
| return stripLeadingSlash(stripTrailingSlash(baseHref)); | |
| } | |
| /// Applies substitutions to the content of the index.html file. | |
| void applySubstitutions({ | |
| required String baseHref, | |
| required String? serviceWorkerVersion, | |
| }) { | |
| if (_content.contains(kBaseHrefPlaceholder)) { | |
| _content = _content.replaceAll(kBaseHrefPlaceholder, baseHref); | |
| } | |
| if (serviceWorkerVersion != null) { | |
| _content = _content | |
| .replaceFirst( | |
| // Support older `var` syntax as well as new `const` syntax | |
| RegExp('(const|var) serviceWorkerVersion = null'), | |
| 'const serviceWorkerVersion = "$serviceWorkerVersion"', | |
| ) | |
| // This is for legacy index.html that still uses the old service | |
| // worker loading mechanism. | |
| .replaceFirst( | |
| "navigator.serviceWorker.register('flutter_service_worker.js')", | |
| "navigator.serviceWorker.register('flutter_service_worker.js?v=$serviceWorkerVersion')", | |
| ); | |
| } | |
| } | |
| } | |
| /// Strips the leading slash from a path. | |
| String stripLeadingSlash(String path) { | |
| while (path.startsWith('/')) { | |
| path = path.substring(1); | |
| } | |
| return path; | |
| } | |
| /// Strips the trailing slash from a path. | |
| String stripTrailingSlash(String path) { | |
| while (path.endsWith('/')) { | |
| path = path.substring(0, path.length - 1); | |
| } | |
| return path; | |
| } | |
| const String _kBasePathExample = ''' | |
| For example, to serve from the root use: | |
| <base href="/"> | |
| To serve from a subpath "foo" (i.e. http://localhost:8080/foo/ instead of http://localhost:8080/) use: | |
| <base href="/foo/"> | |
| For more information, see: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base | |
| '''; |