| 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
|
| -];
|
|
|