Index: web_components/lib/build/import_inliner.dart |
diff --git a/web_components/lib/build/import_inliner.dart b/web_components/lib/build/import_inliner.dart |
deleted file mode 100644 |
index 1f562a7459202f1e2bcf891a30f6b4ed83e0bae6..0000000000000000000000000000000000000000 |
--- a/web_components/lib/build/import_inliner.dart |
+++ /dev/null |
@@ -1,366 +0,0 @@ |
-// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
-library web_components.build.import_inliner; |
- |
-import 'dart:async'; |
-import 'dart:collection' show LinkedHashMap; |
-import 'package:barback/barback.dart'; |
-import 'package:code_transformers/assets.dart'; |
-import 'package:code_transformers/messages/build_logger.dart'; |
-import 'package:html/dom.dart'; |
-import 'package:html/dom_parsing.dart' show TreeVisitor; |
-import 'package:path/path.dart' as path; |
-import 'package:source_span/source_span.dart'; |
-import 'common.dart'; |
-import 'import_crawler.dart'; |
-import 'messages.dart'; |
- |
-/// Transformer which inlines all html imports found from the entry points. This |
-/// deletes all dart scripts found during the inlining, so the |
-/// [ScriptCompactorTransformer] should be ran first if there are any dart files |
-/// in html imports. |
-class ImportInlinerTransformer extends Transformer { |
- final List<String> entryPoints; |
- final List<String> bindingStartDelimiters; |
- |
- ImportInlinerTransformer( |
- [this.entryPoints, this.bindingStartDelimiters = const []]); |
- |
- bool isPrimary(AssetId id) { |
- if (entryPoints != null) return entryPoints.contains(id.path); |
- // If no entry point is supplied, then any html file under web/ or test/ is |
- // an entry point. |
- return (id.path.startsWith('web/') || id.path.startsWith('test/')) && |
- id.path.endsWith('.html'); |
- } |
- |
- apply(Transform transform) { |
- var logger = new BuildLogger(transform, convertErrorsToWarnings: true); |
- return new ImportInliner(transform, transform.primaryInput.id, logger, |
- bindingStartDelimiters: bindingStartDelimiters).run(); |
- } |
-} |
- |
-/// Helper class which actually does all the inlining of html imports for a |
-/// single entry point. |
-class ImportInliner { |
- // Can be an AggregateTransform or Transform |
- final transform; |
- // The primary input to start from. |
- final AssetId primaryInput; |
- // The logger to use. |
- final BuildLogger logger; |
- // The start delimiters for template bindings, such as '{{' or '[['. |
- final List<String> bindingStartDelimiters; |
- |
- ImportInliner(this.transform, this.primaryInput, this.logger, |
- {this.bindingStartDelimiters: const []}); |
- |
- Future run() { |
- var crawler = new ImportCrawler(transform, primaryInput, logger); |
- return crawler.crawlImports().then((imports) { |
- var primaryDocument = imports[primaryInput].document; |
- |
- // Normalize urls in the entry point. |
- var changed = new _UrlNormalizer( |
- primaryInput, primaryInput, logger, bindingStartDelimiters) |
- .visit(primaryDocument); |
- |
- // Inline things if needed, always have at least one (the entry point). |
- if (imports.length > 1) { |
- _inlineImports(primaryDocument, imports); |
- } else if (!changed && |
- primaryDocument.querySelectorAll('link[rel="import"]').where( |
- (import) => import.attributes['type'] != 'css').length == |
- 0) { |
- // If there were no url changes and no imports, then we are done. |
- return; |
- } |
- |
- primaryDocument |
- .querySelectorAll('link[rel="import"]') |
- .where((import) => import.attributes['type'] != 'css') |
- .forEach((element) => element.remove()); |
- |
- transform.addOutput( |
- new Asset.fromString(primaryInput, primaryDocument.outerHtml)); |
- }); |
- } |
- |
- void _inlineImports( |
- Document primaryDocument, LinkedHashMap<AssetId, ImportData> imports) { |
- // Add a hidden div at the top of the body, this is where we will inline |
- // all the imports. |
- var importWrapper = new Element.tag('div')..attributes['hidden'] = ''; |
- var firstElement = primaryDocument.body.firstChild; |
- if (firstElement != null) { |
- primaryDocument.body.insertBefore(importWrapper, firstElement); |
- } else { |
- primaryDocument.body.append(importWrapper); |
- } |
- |
- // Move all scripts/stylesheets/imports into the wrapper to maintain |
- // ordering. |
- _moveHeadToWrapper(primaryDocument, importWrapper); |
- |
- // Add all the other imports! |
- imports.forEach((AssetId asset, ImportData data) { |
- if (asset == primaryInput) return; |
- var document = data.document; |
- // Remove all dart script tags. |
- document |
- .querySelectorAll('script[type="$dartType"]') |
- .forEach((script) => script.remove()); |
- // Normalize urls in attributes and inline css. |
- new _UrlNormalizer(data.fromId, asset, logger, bindingStartDelimiters) |
- .visit(document); |
- // Replace the import with its contents by appending the nodes |
- // immediately before the import one at a time, and then removing the |
- // import from the document. |
- var element = data.element; |
- var parent = element.parent; |
- document.head.nodes |
- .toList(growable: false) |
- .forEach((child) => parent.insertBefore(child, element)); |
- document.body.nodes |
- .toList(growable: false) |
- .forEach((child) => parent.insertBefore(child, element)); |
- element.remove(); |
- }); |
- } |
-} |
- |
-/// To preserve the order of scripts with respect to inlined |
-/// link rel=import, we move both of those into the body before we do any |
-/// inlining. We do not start doing this until the first import is found |
-/// however, as some scripts do need to be ran in the head to work |
-/// properly (webcomponents.js for instance). |
-/// |
-/// Note: we do this for stylesheets as well to preserve ordering with |
-/// respect to eachother, because stylesheets can be pulled in transitively |
-/// from imports. |
-void _moveHeadToWrapper(Document doc, Element wrapper) { |
- var foundImport = false; |
- for (var node in doc.head.nodes.toList(growable: false)) { |
- if (node is! Element) continue; |
- var tag = node.localName; |
- var type = node.attributes['type']; |
- var rel = node.attributes['rel']; |
- if (tag == 'link' && rel == 'import') foundImport = true; |
- if (!foundImport) continue; |
- if (tag == 'style' || |
- tag == 'script' && |
- (type == null || type == jsType || type == dartType) || |
- tag == 'link' && (rel == 'stylesheet' || rel == 'import')) { |
- // Move the node into the wrapper, where its contents will be placed. |
- // This wrapper is a hidden div to prevent inlined html from causing a |
- // FOUC. |
- wrapper.append(node); |
- } |
- } |
-} |
- |
-/// Internally adjusts urls in the html that we are about to inline. |
-// TODO(jakemac): Everything from here down is almost an exact copy from the |
-// polymer package. We should consolidate this logic by either removing it |
-// completely from polymer or exposing it publicly here and using that in |
-// polymer. |
-class _UrlNormalizer extends TreeVisitor { |
- /// [AssetId] for the main entry point. |
- final AssetId primaryInput; |
- |
- /// Asset where the original content (and original url) was found. |
- final AssetId sourceId; |
- |
- /// Counter used to ensure that every library name we inject is unique. |
- int _count = 0; |
- |
- /// Path to the top level folder relative to the transform primaryInput. |
- /// This should just be some arbitrary # of ../'s. |
- final String topLevelPath; |
- |
- /// Whether or not the normalizer has changed something in the tree. |
- bool changed = false; |
- |
- // The start delimiters for template bindings, such as '{{' or '[['. If these |
- // are found before the first `/` in a url, then the url will not be |
- // normalized. |
- final List<String> bindingStartDelimiters; |
- |
- final BuildLogger logger; |
- |
- _UrlNormalizer(AssetId primaryInput, this.sourceId, this.logger, |
- this.bindingStartDelimiters) |
- : primaryInput = primaryInput, |
- topLevelPath = '../' * (path.url.split(primaryInput.path).length - 2); |
- |
- bool visit(Node node) { |
- super.visit(node); |
- return changed; |
- } |
- |
- visitElement(Element node) { |
- // TODO(jakemac): Support custom elements that extend html elements which |
- // have url-like attributes. This probably means keeping a list of which |
- // html elements support each url-like attribute. |
- if (!isCustomTagName(node.localName)) { |
- node.attributes.forEach((name, value) { |
- if (_urlAttributes.contains(name)) { |
- node.attributes[name] = _newUrl(value, node.sourceSpan); |
- changed = value != node.attributes[name]; |
- } |
- }); |
- } |
- if (node.localName == 'style') { |
- node.text = visitCss(node.text); |
- } else if (node.localName == 'script' && |
- node.attributes['type'] == dartType && |
- !node.attributes.containsKey('src')) { |
- changed = true; |
- } |
- return super.visitElement(node); |
- } |
- |
- static final _url = new RegExp(r'url\(([^)]*)\)', multiLine: true); |
- static final _quote = new RegExp('["\']', multiLine: true); |
- |
- /// Visit the CSS text and replace any relative URLs so we can inline it. |
- // Ported from: |
- // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378acc691f/lib/vulcan.js#L149 |
- // TODO(jmesserly): use csslib here instead? Parsing with RegEx is sadness. |
- // Maybe it's reliable enough for finding URLs in CSS? I'm not sure. |
- String visitCss(String cssText) { |
- var url = spanUrlFor(sourceId, primaryInput, logger); |
- var src = new SourceFile(cssText, url: url); |
- return cssText.replaceAllMapped(_url, (match) { |
- changed = true; |
- // Extract the URL, without any surrounding quotes. |
- var span = src.span(match.start, match.end); |
- var href = match[1].replaceAll(_quote, ''); |
- href = _newUrl(href, span); |
- return 'url($href)'; |
- }); |
- } |
- |
- String _newUrl(String href, SourceSpan span) { |
- // We only want to parse the part of the href leading up to the first |
- // folder, anything after that is not informative. |
- var hrefToParse; |
- var firstFolder = href.indexOf('/'); |
- if (firstFolder == -1) { |
- hrefToParse = href; |
- } else if (firstFolder == 0) { |
- return href; |
- } else { |
- // Special case packages and assets urls. |
- if (href.contains('packages/')) { |
- var suffix = href.substring(href.indexOf('packages/') + 9); |
- return '${topLevelPath}packages/$suffix'; |
- } else if (href.contains('assets/')) { |
- var suffix = href.substring(href.indexOf('assets/') + 7); |
- return '${topLevelPath}packages/$suffix'; |
- } |
- |
- hrefToParse = '${href.substring(0, firstFolder + 1)}'; |
- } |
- |
- // If we found a binding before the first `/`, then just return the original |
- // href, we can't determine anything about it. |
- if (bindingStartDelimiters.any((d) => hrefToParse.contains(d))) return href; |
- |
- Uri uri; |
- // Various template systems introduce invalid characters to uris which would |
- // be typically replaced at runtime. Parse errors are assumed to be caused |
- // by this, and we just return the original href in that case. |
- try { |
- uri = Uri.parse(hrefToParse); |
- } catch (e) { |
- return href; |
- } |
- if (uri.isAbsolute) return href; |
- if (uri.scheme.isNotEmpty) return href; |
- if (uri.host.isNotEmpty) return href; |
- if (uri.path.isEmpty) return href; // Implies standalone ? or # in URI. |
- if (path.isAbsolute(hrefToParse)) return href; |
- |
- var id = uriToAssetId(sourceId, hrefToParse, logger, span); |
- if (id == null) return href; |
- |
- // Build the new path, placing back any suffixes that we stripped earlier. |
- var prefix = |
- (firstFolder == -1) ? id.path : id.path.substring(0, id.path.length); |
- var suffix = (firstFolder == -1) ? '' : href.substring(firstFolder); |
- var newPath = '$prefix$suffix'; |
- |
- if (newPath.startsWith('lib/')) { |
- return '${topLevelPath}packages/${id.package}/${newPath.substring(4)}'; |
- } |
- |
- if (newPath.startsWith('asset/')) { |
- return '${topLevelPath}assets/${id.package}/${newPath.substring(6)}'; |
- } |
- |
- if (primaryInput.package != id.package) { |
- // Technically we shouldn't get there |
- logger.error(internalErrorDontKnowHowToImport |
- .create({'target': id, 'source': primaryInput, 'extra': ''}), |
- span: span); |
- return href; |
- } |
- |
- var builder = path.url; |
- return builder.normalize(builder.relative(builder.join('/', newPath), |
- from: builder.join('/', builder.dirname(primaryInput.path)))); |
- } |
-} |
- |
-/// Returns true if this is a valid custom element name. See: |
-/// <http://w3c.github.io/webcomponents/spec/custom/#dfn-custom-element-type> |
-bool isCustomTagName(String name) { |
- if (name == null || !name.contains('-')) return false; |
- return !invalidTagNames.containsKey(name); |
-} |
- |
-/// These names have meaning in SVG or MathML, so they aren't allowed as custom |
-/// tags. See [isCustomTagName]. |
-const invalidTagNames = const { |
- 'annotation-xml': '', |
- 'color-profile': '', |
- 'font-face': '', |
- 'font-face-src': '', |
- 'font-face-uri': '', |
- 'font-face-format': '', |
- 'font-face-name': '', |
- 'missing-glyph': '', |
-}; |
- |
-/// HTML attributes that expect a URL value. |
-/// <http://dev.w3.org/html5/spec/section-index.html#attributes-1> |
-/// |
-/// Every one of these attributes is a URL in every context where it is used in |
-/// the DOM. The comments show every DOM element where an attribute can be used. |
-/// |
-/// The _* version of each attribute is also supported, see http://goo.gl/5av8cU |
-const _urlAttributes = const [ |
- 'action', |
- '_action', // in form |
- 'background', |
- '_background', // in body |
- 'cite', |
- '_cite', // in blockquote, del, ins, q |
- 'data', |
- '_data', // in object |
- 'formaction', |
- '_formaction', // in button, input |
- 'href', |
- '_href', // in a, area, link, base, command |
- 'icon', |
- '_icon', // in command |
- 'manifest', |
- '_manifest', // in html |
- 'poster', |
- '_poster', // in video |
- 'src', |
- '_src', // in audio, embed, iframe, img, input, script, source, track,video |
-]; |