| // 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 |
| '''; |