blob: f8ffaa273b8be8d577b04b39598f04cebee59c7f [file] [log] [blame]
// 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.html_import_annotation_inliner;
import 'dart:async';
import 'package:analyzer/analyzer.dart';
import 'package:barback/barback.dart';
import 'package:html5lib/dom.dart' as dom;
import 'package:html5lib/parser.dart';
import 'package:initialize/plugin_transformer.dart';
import 'package:source_maps/refactor.dart';
import '../src/normalize_path.dart';
/// Given an html entry point with a single dart bootstrap file created by the
/// `initialize` transformer, this will open that dart file and remove all
/// `HtmlImport` initializers from it. Then it appends those imports to the head
/// of the html entry point.
/// Notes: Does not inline the import, it just puts the <link rel="import"> tag.
/// This also has a few limitations, it only supports string literals (to avoid
/// using the analyzer to resolve references) and doesn't support const
/// references to HtmlImport annotations (for the same reason).
class HtmlImportAnnotationInliner extends InitializePluginTransformer {
final String _bootstrapFile;
final String _htmlEntryPoint;
TransformLogger _logger;
final Set<String> importPaths = new Set();
HtmlImportAnnotationInliner(String bootstrapFile, this._htmlEntryPoint)
: super(bootstrapFile),
_bootstrapFile = bootstrapFile;
factory HtmlImportAnnotationInliner.asPlugin(BarbackSettings settings) {
var bootstrapFile = settings.configuration['bootstrap_file'];
if (bootstrapFile is! String || !bootstrapFile.endsWith('.dart')) {
throw new ArgumentError(
'`bootstrap_file` should be a string path to a dart file');
}
var htmlEntryPoint = settings.configuration['html_entry_point'];
if (htmlEntryPoint is! String || !htmlEntryPoint.endsWith('.html')) {
throw new ArgumentError(
'`html_entry_point` should be a string path to an html file');
}
return new HtmlImportAnnotationInliner(bootstrapFile, htmlEntryPoint);
}
classifyPrimary(AssetId id) {
var superValue = super.classifyPrimary(id);
if (superValue != null) return superValue;
// Group it with the bootstrap file.
if (_htmlEntryPoint == id.path) return _bootstrapFile;
return null;
}
apply(AggregateTransform transform) {
_logger = transform.logger;
return super.apply(transform).then((_) {
var htmlEntryPoint =
allAssets.firstWhere((asset) => asset.id.path == _htmlEntryPoint);
return htmlEntryPoint.readAsString().then((html) {
var doc = parse(html);
for (var importPath in importPaths) {
var import = new dom.Element.tag('link')
..attributes = {'rel': 'import', 'href': importPath,};
doc.head.append(import);
}
transform
.addOutput(new Asset.fromString(htmlEntryPoint.id, doc.outerHtml));
});
});
}
// Executed for each initializer constructor in the bootstrap file. We filter
// out the HtmlImport ones and inline them.
initEntry(
InstanceCreationExpression expression, TextEditTransaction transaction) {
// Filter out extraneous values.
if (expression is! InstanceCreationExpression) return;
var args = expression.argumentList.arguments;
// Only support InstanceCreationExpressions. Const references to HtmlImport
// annotations can't be cheaply discovered.
if (args[0] is! InstanceCreationExpression) return;
if (!'${args[0].constructorName.type.name}'.contains('.HtmlImport')) return;
// Grab the raw path supplied to the HtmlImport. Only string literals are
// supported for the transformer.
var originalPath = args[0].argumentList.arguments[0];
if (originalPath is SimpleStringLiteral) {
originalPath = originalPath.value;
} else {
_logger.warning('Found HtmlImport constructor which was supplied an '
'expression. Only raw strings are currently supported for the '
'transformer, so $originalPath will be injected dynamically');
return;
}
// Now grab the package from the LibraryIdentifier, we know its either a
// string or null literal.
var package = args[1].argumentList.arguments[1];
if (package is SimpleStringLiteral) {
package = package.value;
} else if (package is NullLiteral) {
package = null;
} else {
_logger.error('Invalid LibraryIdentifier declaration. The 2nd argument '
'be a literal string or null. `${args[1]}`');
}
// And finally get the original dart file path, this is always a string
// literal.
var dartPath = args[1].argumentList.arguments[2];
if (dartPath is SimpleStringLiteral) {
dartPath = dartPath.value;
} else {
_logger.error('Invalid LibraryIdentifier declaration. The 3rd argument '
'be a literal string. `${args[1]}`');
}
// Add the normalized path to our list and remove the expression from the
// bootstrap file.
importPaths.add(normalizeHtmlImportPath(originalPath, package, dartPath));
removeInitializer(expression, transaction);
}
}