refactor(dart/transform): Compose NgDepsModel & NgMeta phases

Link `NgDeps` & `NgMeta` data in the same phase to avoid unnecessary asset
creation & reading.

Remove `NgMeta#exports` and instead use `NgDeps#exports` to link `NgMeta` files
in `ng_meta_linker.dart`.
This commit is contained in:
Tim Blasi
2015-10-01 11:05:43 -07:00
parent 75187d605b
commit 4ac29621f4
35 changed files with 821 additions and 783 deletions

View File

@ -8,16 +8,17 @@ import 'package:path/path.dart' as path;
/// If `fromAbsolute` is specified, `importPath` may be a relative path,
/// otherwise it is expected to be absolute.
String writeImportUri(String importPath, {String prefix, String fromAbsolute}) {
var urlResolver = const TransformerUrlResolver();
var codegenImportPath;
var resolver = const TransformerUrlResolver();
var importUri = resolver.toAssetScheme(Uri.parse(importPath));
var importUri =
toAssetScheme(Uri.parse(urlResolver.resolve(fromAbsolute, importPath)));
if (_canPackageImport(importUri) ||
fromAbsolute == null ||
fromAbsolute.isEmpty) {
codegenImportPath = _toPackageImport(importUri);
} else {
var moduleUri = resolver.toAssetScheme(Uri.parse(fromAbsolute));
var moduleUri = toAssetScheme(Uri.parse(fromAbsolute));
if (_canImportRelative(importUri, from: moduleUri)) {
codegenImportPath = path.url.relative(importUri.toString(),
from: path.dirname(moduleUri.toString()));

View File

@ -2,74 +2,123 @@ library angular2.transform.common.ng_meta;
import 'package:angular2/src/core/compiler/directive_metadata.dart';
import 'logging.dart';
import 'model/ng_deps_model.pb.dart';
import 'url_resolver.dart' show isDartCoreUri;
/// Metadata about directives and directive aliases.
/// Metadata about directives, directive aliases, and injectable values.
///
/// [NgMeta] is used in three stages of the transformation process. First we
/// store directive aliases exported from a single file in an [NgMeta] instance.
/// Later we use another [NgMeta] instance to store more information about a
/// single file, including both directive aliases and directives extracted from
/// the corresponding `.ng_deps.dart` file. Further down the compilation
/// process, the template compiler needs to reason about the namespace of import
/// prefixes, so it will combine multple [NgMeta] instances together if they
/// were imported into a file with the same prefix.
/// [NgMeta] is used in three stages of the transformation process:
///
/// Instances of this class are serialized into `.aliases.json` and
/// `.ng_meta.json` files as intermediate assets to make the compilation process
/// easier.
/// First we store directive aliases and types exported directly (that is, not
/// via an `export` statement) from a single file in an [NgMeta] instance.
///
/// In the second phase, we perform two actions:
/// 1. Incorporate all the data from [NgMeta] instances created by all
/// files `exported` by the original file, such that all aliases and types
/// visible when importing the original file are stored in its associated
/// [NgMeta] instance.
/// 2. Use the [NgDepsModel] to write Dart code registering all injectable
/// values with the Angular 2 runtime reflection system.
///
/// Further down the compilation process, the template compiler needs to reason
/// about the namespace of import prefixes, so it will combine multiple [NgMeta]
/// instances together if they were imported into a file with the same prefix.
///
/// Instances of this class are serialized into `.ng_meta.json` files as
/// intermediate assets to make the compilation process easier.
class NgMeta {
static const _ALIAS_VALUE = 'alias';
static const _KIND_KEY = 'kind';
static const _NG_DEPS_KEY = 'ngDeps';
static const _TYPE_VALUE = 'type';
static const _VALUE_KEY = 'value';
/// Directive metadata for each type annotated as a directive.
final Map<String, CompileDirectiveMetadata> types;
/// List of other types and names associated with a given name.
final Map<String, List<String>> aliases;
/// TODO(kegluneq): Once merged with NgDepsModel, use its exports.
final List<String> exports;
// The NgDeps generated from
final NgDepsModel ngDeps;
NgMeta(this.types, this.aliases, this.exports);
NgMeta(
{Map<String, CompileDirectiveMetadata> types,
Map<String, List<String>> aliases,
this.ngDeps: null})
: this.types = types != null ? types : {},
this.aliases = aliases != null ? aliases : {};
NgMeta.empty() : this({}, {}, []);
NgMeta.empty() : this();
bool get isEmpty => types.isEmpty && aliases.isEmpty && exports.isEmpty;
// `model` can be an `ImportModel` or `ExportModel`.
static bool _isDartImport(dynamic model) => isDartCoreUri(model.uri);
bool get isNgDepsEmpty {
if (ngDeps == null) return true;
// If this file imports only dart: libraries and does not define any
// reflectables of its own, it doesn't need a .ng_deps.dart file.
if (ngDeps.reflectables == null || ngDeps.reflectables.isEmpty) {
if ((ngDeps.imports == null || ngDeps.imports.every(_isDartImport)) &&
(ngDeps.exports == null || ngDeps.exports.every(_isDartImport))) {
return true;
}
}
return false;
}
bool get isEmpty => types.isEmpty && aliases.isEmpty && isNgDepsEmpty;
/// Parse from the serialized form produced by [toJson].
factory NgMeta.fromJson(Map json) {
var exports = <String>[];
var types = {};
var aliases = {};
var ngDeps = null;
final types = {};
final aliases = {};
for (var key in json.keys) {
if (key == '__exports__') {
exports = json[key];
if (key == _NG_DEPS_KEY) {
var ngDepsJsonMap = json[key];
if (ngDepsJsonMap == null) continue;
if (ngDepsJsonMap is! Map) {
logger.warning(
'Unexpected value $ngDepsJsonMap for key "$key" in NgMeta.');
continue;
}
ngDeps = new NgDepsModel()..mergeFromJsonMap(ngDepsJsonMap);
} else {
var entry = json[key];
if (entry['kind'] == 'type') {
types[key] = CompileDirectiveMetadata.fromJson(entry['value']);
} else if (entry['kind'] == 'alias') {
aliases[key] = entry['value'];
if (entry is! Map) {
logger.warning('Unexpected value $entry for key "$key" in NgMeta.');
continue;
}
if (entry[_KIND_KEY] == _TYPE_VALUE) {
types[key] = CompileDirectiveMetadata.fromJson(entry[_VALUE_KEY]);
} else if (entry[_KIND_KEY] == _ALIAS_VALUE) {
aliases[key] = entry[_VALUE_KEY];
}
}
}
return new NgMeta(types, aliases, exports);
return new NgMeta(types: types, aliases: aliases, ngDeps: ngDeps);
}
/// Serialized representation of this instance.
Map toJson() {
Map toJson({bool withNgDeps: true}) {
var result = {};
result['__exports__'] = exports;
if (withNgDeps) {
result[_NG_DEPS_KEY] = isNgDepsEmpty ? null : ngDeps.writeToJsonMap();
}
types.forEach((k, v) {
result[k] = {'kind': 'type', 'value': v.toJson()};
result[k] = {_KIND_KEY: _TYPE_VALUE, _VALUE_KEY: v.toJson()};
});
aliases.forEach((k, v) {
result[k] = {'kind': 'alias', 'value': v};
result[k] = {_KIND_KEY: _ALIAS_VALUE, _VALUE_KEY: v};
});
return result;
}
/// Merge into this instance all information from [other].
/// This does not include `exports`.
/// This does not include `ngDeps`.
void addAll(NgMeta other) {
types.addAll(other.types);
aliases.addAll(other.aliases);

View File

@ -5,9 +5,6 @@ import 'package:glob/glob.dart';
import 'annotation_matcher.dart';
import 'mirror_mode.dart';
/// See `optimizationPhases` below for an explanation.
const DEFAULT_OPTIMIZATION_PHASES = 5;
const CUSTOM_ANNOTATIONS_PARAM = 'custom_annotations';
const ENTRY_POINT_PARAM = 'entry_points';
const FORMAT_CODE_PARAM = 'format_code';
@ -41,15 +38,6 @@ class TransformerOptions {
final bool reflectPropertiesAsAttributes;
/// The number of phases to spend optimizing output size.
/// Each additional phase adds time to the transformation but may decrease
/// final output size. There is a limit beyond which this will no longer
/// decrease size, that is, setting this to 20 may not decrease size any
/// more than setting it to 10, but you will still pay an additional
/// penalty in transformation time.
/// The "correct" number of phases varies with the structure of the app.
final int optimizationPhases;
/// Whether to format generated code.
/// Code that is only modified will never be formatted because doing so may
/// invalidate the source maps generated by `dart2js` and/or other tools.
@ -62,7 +50,6 @@ class TransformerOptions {
this.mirrorMode,
this.initReflector,
this.annotationMatcher,
this.optimizationPhases,
{this.reflectPropertiesAsAttributes,
this.formatCode});
@ -71,24 +58,16 @@ class TransformerOptions {
MirrorMode mirrorMode: MirrorMode.none,
bool initReflector: true,
List<ClassDescriptor> customAnnotationDescriptors: const [],
int optimizationPhases: DEFAULT_OPTIMIZATION_PHASES,
bool inlineViews: true,
bool reflectPropertiesAsAttributes: true,
bool formatCode: false}) {
var annotationMatcher = new AnnotationMatcher()
..addAll(customAnnotationDescriptors);
optimizationPhases = optimizationPhases.isNegative ? 0 : optimizationPhases;
var entryPointGlobs = entryPoints != null
? entryPoints.map((path) => new Glob(path)).toList(growable: false)
: null;
return new TransformerOptions._internal(
entryPoints,
entryPointGlobs,
modeName,
mirrorMode,
initReflector,
annotationMatcher,
optimizationPhases,
return new TransformerOptions._internal(entryPoints, entryPointGlobs,
modeName, mirrorMode, initReflector, annotationMatcher,
reflectPropertiesAsAttributes: reflectPropertiesAsAttributes,
formatCode: formatCode);
}

View File

@ -28,14 +28,11 @@ TransformerOptions parseBarbackSettings(BarbackSettings settings) {
mirrorMode = MirrorMode.none;
break;
}
var optimizationPhases = _readInt(config, OPTIMIZATION_PHASES_PARAM,
defaultValue: DEFAULT_OPTIMIZATION_PHASES);
return new TransformerOptions(entryPoints,
modeName: settings.mode.name,
mirrorMode: mirrorMode,
initReflector: initReflector,
customAnnotationDescriptors: _readCustomAnnotations(config),
optimizationPhases: optimizationPhases,
reflectPropertiesAsAttributes: reflectPropertiesAsAttributes,
formatCode: formatCode);
}
@ -68,18 +65,6 @@ List<String> _readFileList(Map config, String paramName) {
return files;
}
int _readInt(Map config, String paramName, {int defaultValue: null}) {
if (!config.containsKey(paramName)) return defaultValue;
var value = config[paramName];
if (value is String) {
value = int.parse(value);
}
if (value is! int) {
throw new ArgumentError.value(value, paramName, 'Expected an integer');
}
return value;
}
/// Parse the [CUSTOM_ANNOTATIONS_PARAM] options out of the transformer into
/// [ClassDescriptor]s.
List<ClassDescriptor> _readCustomAnnotations(Map config) {

View File

@ -1,57 +1,83 @@
library angular2.transform.template_compiler.url_resolver;
import 'package:angular2/src/core/compiler/url_resolver.dart';
import 'package:angular2/src/core/services.dart';
import 'package:barback/barback.dart';
class TransformerUrlResolver implements UrlResolver {
const TransformerUrlResolver();
@override
String resolve(String baseUrl, String url) {
if (url == null) throw new ArgumentError.notNull('url');
Uri uri = Uri.parse(url);
if (!uri.isAbsolute) {
if (baseUrl == null) throw new ArgumentError.notNull('baseUrl');
if (baseUrl.isEmpty) throw new ArgumentError.value(
'(empty string)', 'baseUrl');
uri = Uri.parse(baseUrl).resolveUri(uri);
}
return toAssetScheme(uri).toString();
}
}
/// Converts `absoluteUri` to use the 'asset' scheme used in the Angular 2
/// template compiler.
///
/// The `scheme` of `absoluteUri` is expected to be either 'package' or
/// 'asset'.
Uri toAssetScheme(Uri absoluteUri) {
if (absoluteUri == null) return null;
String toAssetUri(AssetId assetId) {
if (assetId == null) throw new ArgumentError.notNull('assetId');
return 'asset:${assetId.package}/${assetId.path}';
}
if (!absoluteUri.isAbsolute) {
throw new ArgumentError.value(
absoluteUri, 'absoluteUri', 'Value passed must be an absolute uri');
}
if (absoluteUri.scheme == 'asset') {
if (absoluteUri.pathSegments.length < 3) {
throw new FormatException(
'An asset: URI must have at least 3 path '
'segments, for example '
'asset:<package-name>/<first-level-dir>/<path-to-dart-file>.',
absoluteUri);
}
return absoluteUri;
}
if (absoluteUri.scheme != 'package') {
throw new ArgumentError.value(
absoluteUri, 'absoluteUri', 'Unsupported URI scheme encountered');
}
AssetId fromUri(String assetUri) {
if (assetUri == null) throw new ArgumentError.notNull('assetUri');
if (assetUri.isEmpty) throw new ArgumentError.value(
'(empty string)', 'assetUri');
var uri = toAssetScheme(Uri.parse(assetUri));
return new AssetId(
uri.pathSegments.first, uri.pathSegments.skip(1).join('/'));
}
if (absoluteUri.pathSegments.length < 2) {
/// Converts `absoluteUri` to use the 'asset' scheme used in the Angular 2
/// template compiler.
///
/// The `scheme` of `absoluteUri` is expected to be either 'package' or
/// 'asset'.
Uri toAssetScheme(Uri absoluteUri) {
if (absoluteUri == null) throw new ArgumentError.notNull('absoluteUri');
if (!absoluteUri.isAbsolute) {
throw new ArgumentError.value(
absoluteUri, 'absoluteUri', 'Value passed must be an absolute uri');
}
if (absoluteUri.scheme == 'asset') {
if (absoluteUri.pathSegments.length < 3) {
throw new FormatException(
'A package: URI must have at least 2 path '
'An asset: URI must have at least 3 path '
'segments, for example '
'package:<package-name>/<path-to-dart-file>',
'asset:<package-name>/<first-level-dir>/<path-to-dart-file>.',
absoluteUri);
}
var pathSegments = absoluteUri.pathSegments.toList()..insert(1, 'lib');
return new Uri(scheme: 'asset', pathSegments: pathSegments);
return absoluteUri;
}
if (absoluteUri.scheme != 'package') {
throw new FormatException(
'Unsupported URI scheme "${absoluteUri.scheme}" encountered.',
absoluteUri);
}
if (absoluteUri.pathSegments.length < 2) {
throw new FormatException(
'A package: URI must have at least 2 path '
'segments, for example '
'package:<package-name>/<path-to-dart-file>',
absoluteUri);
}
var pathSegments = absoluteUri.pathSegments.toList()..insert(1, 'lib');
return new Uri(scheme: 'asset', pathSegments: pathSegments);
}
bool isDartCoreUri(String uri) {
if (uri == null) throw new ArgumentError.notNull('uri');
if (uri.isEmpty) throw new ArgumentError.value('(empty string)', 'uri');
return uri.startsWith('dart:');
}

View File

@ -3,20 +3,25 @@ library angular2.transform.template_compiler.xhr_impl;
import 'dart:async';
import 'package:angular2/src/core/compiler/xhr.dart' show XHR;
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:barback/barback.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/url_resolver.dart';
/// Transformer-specific implementation of XHR that is backed by an
/// [AssetReader].
///
/// This implementation expects urls using the asset: scheme.
/// See [src/transform/common/url_resolver.dart] for a way to convert package:
/// and relative urls to asset: urls.
class XhrImpl implements XHR {
final AssetReader _reader;
XhrImpl(this._reader);
Future<String> get(String url) async {
final uri = Uri.parse(url);
if (uri.scheme != 'asset') {
throw new FormatException('Unsupported uri encountered: $uri', url);
final assetId = fromUri(url);
if (!url.startsWith('asset:')) {
logger.warning('XhrImpl received unexpected url: $url');
}
final assetId =
new AssetId(uri.pathSegments.first, uri.pathSegments.skip(1).join('/'));
if (!await _reader.hasInput(assetId)) {
throw new ArgumentError.value('Could not read asset at uri $url', 'url');

View File

@ -1,118 +0,0 @@
library angular2.transform.directive_linker.linker;
import 'dart:async';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/model/import_export_model.pb.dart';
import 'package:angular2/src/transform/common/model/ng_deps_model.pb.dart';
import 'package:barback/barback.dart';
import 'package:code_transformers/assets.dart';
/// Checks the `.ng_deps.json` file represented by `entryPoint` and
/// determines whether it is necessary to the functioning of the Angular 2
/// Dart app.
///
/// An `.ng_deps.json` file is not necessary if:
/// 1. It does not register any `@Injectable` types with the system.
/// 2. It does not import any libraries whose `.ng_deps.json` files register
/// any `@Injectable` types with the system.
///
/// Since `@Directive` and `@Component` inherit from `@Injectable`, we know
/// we will not miss processing any classes annotated with those tags.
Future<bool> isNecessary(AssetReader reader, AssetId entryPoint) async {
if (!(await reader.hasInput(entryPoint))) return false;
var jsonString = await reader.readAsString(entryPoint);
if (jsonString == null || jsonString.isEmpty) return false;
var ngDepsModel = new NgDepsModel.fromJson(jsonString);
if (ngDepsModel.reflectables != null &&
ngDepsModel.reflectables.isNotEmpty) return true;
// We do not register any @Injectables, do we call any dependencies?
var linkedDepsMap = await _processNgImports(reader, entryPoint, ngDepsModel);
return linkedDepsMap.isNotEmpty;
}
/// Modifies the [NgDepsModel] represented by `entryPoint` to import its
/// dependencies' associated `.ng_deps.dart` files.
///
/// For example, if entry_point.ng_deps.dart imports dependency.dart, this
/// will check if dependency.ng_deps.json exists. If it does, we add an import
/// to dependency.ng_deps.dart to the entry_point [NgDepsModel] and set
/// `isNgDeps` to `true` to signify that it is a dependency on which we need to
/// call `initReflector`.
Future<NgDepsModel> linkNgDeps(AssetReader reader, AssetId entryPoint) async {
if (!(await reader.hasInput(entryPoint))) return null;
var jsonString = await reader.readAsString(entryPoint);
if (jsonString.isEmpty) return null;
var ngDepsModel = new NgDepsModel.fromJson(jsonString);
var linkedDepsMap = await _processNgImports(reader, entryPoint, ngDepsModel);
if (linkedDepsMap.isEmpty) {
// We are not calling `initReflector` on any other libraries, but we still
// return the model to ensure it is written to code.
// TODO(kegluneq): Continue using the protobuf format after this phase.
return ngDepsModel;
}
for (var i = ngDepsModel.imports.length - 1; i >= 0; --i) {
var import = ngDepsModel.imports[i];
if (linkedDepsMap.containsKey(import.uri)) {
var linkedModel = new ImportModel()
..isNgDeps = true
..uri = toDepsExtension(linkedDepsMap[import.uri])
..prefix = 'i$i';
// TODO(kegluneq): Preserve combinators?
ngDepsModel.imports.insert(i + 1, linkedModel);
}
}
for (var i = 0, iLen = ngDepsModel.exports.length; i < iLen; ++i) {
var export = ngDepsModel.exports[i];
if (linkedDepsMap.containsKey(export.uri)) {
var linkedModel = new ImportModel()
..isNgDeps = true
..uri = toDepsExtension(linkedDepsMap[export.uri])
..prefix = 'i${ngDepsModel.imports.length}';
// TODO(kegluneq): Preserve combinators?
ngDepsModel.imports.add(linkedModel);
}
}
return ngDepsModel;
}
bool _isNotDartDirective(dynamic model) {
return !model.uri.startsWith('dart:');
}
/// Maps the `uri` of each input [ImportModel] or [ExportModel] to its
/// associated `.ng_deps.json` file, if one exists.
Future<Map<String, String>> _processNgImports(
AssetReader reader, AssetId ngJsonAsset, NgDepsModel model) {
final nullFuture = new Future.value(null);
final importsAndExports = new List.from(model.imports)..addAll(model.exports);
final retVal = <String, String>{};
final entryPoint =
new AssetId(ngJsonAsset.package, toDepsExtension(ngJsonAsset.path));
return Future
.wait(
importsAndExports.where(_isNotDartDirective).map((dynamic directive) {
// The uri of the import or export with .dart replaced with .ng_deps.json.
// This is the json file containing Angular 2 codegen info, if one exists.
var linkedJsonUri = toJsonExtension(directive.uri);
var spanArg = null;
var linkedNgJsonAsset = uriToAssetId(
entryPoint, linkedJsonUri, logger, spanArg,
errorOnAbsolute: false);
if (linkedNgJsonAsset == ngJsonAsset) return nullFuture;
return reader.hasInput(linkedNgJsonAsset).then((hasInput) {
if (hasInput) {
retVal[directive.uri] = linkedJsonUri;
}
}, onError: (_) => null);
}))
.then((_) => retVal);
}

View File

@ -1,84 +0,0 @@
library angular2.transform.directive_linker.transformer;
import 'dart:async';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/code/ng_deps_code.dart';
import 'package:angular2/src/transform/common/formatter.dart';
import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart';
import 'package:barback/barback.dart';
import 'linker.dart';
/// Transformer responsible for processing `.ng_deps.json` files created by
/// {@link DirectiveProcessor} and ensuring that each imports its dependencies'
/// .ng_deps.dart files.
class DirectiveLinker extends Transformer implements DeclaringTransformer {
DirectiveLinker();
@override
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_JSON_EXTENSION);
@override
declareOutputs(DeclaringTransform transform) {
// TODO(kegluenq): We should consume this, but doing so causes barback to
// incorrectly determine what assets are available in this phase.
// transform.consumePrimary();
transform.declareOutput(_depsAssetId(transform.primaryId));
}
@override
Future apply(Transform transform) async {
await log.initZoned(transform, () async {
var reader = new AssetReader.fromTransform(transform);
var primaryId = transform.primaryInput.id;
var ngDepsModel = await linkNgDeps(reader, primaryId);
// See above
// transform.consumePrimary();
var outputAssetId = _depsAssetId(primaryId);
if (ngDepsModel != null) {
var buf = new StringBuffer();
var writer = new NgDepsWriter(buf);
writer.writeNgDepsModel(ngDepsModel);
var formattedCode = formatter.format('$buf', uri: primaryId.path);
transform.addOutput(new Asset.fromString(outputAssetId, formattedCode));
} else {
transform.addOutput(new Asset.fromString(outputAssetId, ''));
}
});
}
}
AssetId _depsAssetId(AssetId primaryId) =>
new AssetId(primaryId.package, toDepsExtension(primaryId.path));
/// Transformer responsible for removing unnecessary `.ng_deps.json` files
/// created by {@link DirectiveProcessor}.
class EmptyNgDepsRemover extends Transformer implements DeclaringTransformer {
EmptyNgDepsRemover();
@override
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_JSON_EXTENSION);
/// We occasionally consume the primary input, but that depends on the
/// contents of the file, so we conservatively declare that we both consume
/// and output the asset. This prevents barback from making any assumptions
/// about the existence of the assets until after the transformer has run.
@override
declareOutputs(DeclaringTransform transform) {
transform.consumePrimary();
transform.declareOutput(transform.primaryId);
}
@override
Future apply(Transform transform) async {
await log.initZoned(transform, () async {
var reader = new AssetReader.fromTransform(transform);
transform.consumePrimary();
if ((await isNecessary(reader, transform.primaryInput.id))) {
transform.addOutput(transform.primaryInput);
}
});
}
}

View File

@ -1,78 +0,0 @@
library angular2.transform.directive_metadata_linker.linker;
import 'dart:async';
import 'dart:convert';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/ng_meta.dart';
import 'package:barback/barback.dart';
import 'package:code_transformers/assets.dart';
import 'package:path/path.dart' as path;
/// Returns [NgMeta] associated with [entryPoint] combined with the [NgMeta] of
/// all files `export`ed from the original file.
///
/// This includes entries for every `Directive`-annotated class and
/// constants that match the directive-aliases pattern.
///
/// There are entries for each of these which is visible from a file importing
/// the original .dart file that produced `entryPoint`. That is, this includes
/// all `Directive` annotated public classes in that file, all `DirectiveAlias`
/// annotated public variables, and any of those entries which are visible from
/// files which the .dart file `export`ed.
///
/// Returns an empty [NgMeta] if there are no `Directive`-annotated classes or
/// `DirectiveAlias` annotated constants in `entryPoint`.
Future<NgMeta> linkDirectiveMetadata(AssetReader reader, AssetId entryPoint) {
return _linkDirectiveMetadataRecursive(
reader, entryPoint, new Set<AssetId>());
}
final _nullFuture = new Future.value(null);
// TODO(kegluneq): Don't reinvent the wheel? Centalize?
AssetId _fromPackageUri(String packageUri) {
var pathParts = path.url.split(packageUri);
return new AssetId(pathParts[0].substring('package:'.length),
'lib/${pathParts.getRange(1, pathParts.length).join('/')}');
}
Future<NgMeta> _linkDirectiveMetadataRecursive(
AssetReader reader, AssetId entryPoint, Set<AssetId> seen) async {
if (entryPoint == null) {
return new NgMeta.empty();
}
// Break cycles, if they exist.
if (seen.contains(entryPoint)) return _nullFuture;
seen.add(entryPoint);
if (!(await reader.hasInput(entryPoint))) return new NgMeta.empty();
var ngMetaJson = await reader.readAsString(entryPoint);
if (ngMetaJson == null || ngMetaJson.isEmpty) return new NgMeta.empty();
var ngMeta = new NgMeta.fromJson(JSON.decode(ngMetaJson));
if (ngMeta.exports == null) return ngMeta;
// Recursively add NgMeta files from `exports`.
return Future.wait(ngMeta.exports.map((uri) {
if (uri.startsWith('dart:')) return _nullFuture;
var metaUri = toMetaExtension(uri);
var assetId;
if (uri.startsWith('package:')) {
assetId = _fromPackageUri(metaUri);
} else {
assetId = uriToAssetId(entryPoint, metaUri, logger, null /* span */,
errorOnAbsolute: false);
}
return _linkDirectiveMetadataRecursive(reader, assetId, seen)
.then((exportedNgMeta) {
if (exportedNgMeta != null) {
ngMeta.addAll(exportedNgMeta);
}
});
})).then((_) => ngMeta);
}

View File

@ -0,0 +1,88 @@
library angular2.transform.directive_metadata_linker.ng_deps_linker;
import 'dart:async';
import 'package:angular2/src/core/services.dart';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/model/import_export_model.pb.dart';
import 'package:angular2/src/transform/common/model/ng_deps_model.pb.dart';
import 'package:angular2/src/transform/common/url_resolver.dart';
import 'package:barback/barback.dart';
/// Modifies the [NgDepsModel] represented by `entryPoint` to import its
/// dependencies' associated `.ng_deps.dart` files.
///
/// For example, if entry_point.ng_deps.dart imports dependency.dart, this
/// will check if dependency.ng_meta.json exists. If it does, we add an import
/// to dependency.ng_deps.dart to the entry_point [NgDepsModel] and set
/// `isNgDeps` to `true` to signify that it is a dependency on which we need to
/// call `initReflector`.
Future<NgDepsModel> linkNgDeps(NgDepsModel ngDepsModel, AssetReader reader,
AssetId entryPoint, UrlResolver resolver) async {
if (ngDepsModel == null) return null;
var linkedDepsMap =
await _processNgImports(ngDepsModel, reader, entryPoint, resolver);
if (linkedDepsMap.isEmpty) {
// We are not calling `initReflector` on any other libraries, but we still
// return the model to ensure it is written to code.
// TODO(kegluneq): Continue using the protobuf format after this phase.
return ngDepsModel;
}
for (var i = ngDepsModel.imports.length - 1; i >= 0; --i) {
var import = ngDepsModel.imports[i];
if (linkedDepsMap.containsKey(import.uri)) {
var linkedModel = new ImportModel()
..isNgDeps = true
..uri = toDepsExtension(import.uri)
..prefix = 'i$i';
// TODO(kegluneq): Preserve combinators?
ngDepsModel.imports.insert(i + 1, linkedModel);
}
}
for (var i = 0, iLen = ngDepsModel.exports.length; i < iLen; ++i) {
var export = ngDepsModel.exports[i];
if (linkedDepsMap.containsKey(export.uri)) {
var linkedModel = new ImportModel()
..isNgDeps = true
..uri = toDepsExtension(export.uri)
..prefix = 'i${ngDepsModel.imports.length}';
// TODO(kegluneq): Preserve combinators?
ngDepsModel.imports.add(linkedModel);
}
}
return ngDepsModel;
}
bool _isNotDartDirective(dynamic model) => !isDartCoreUri(model.uri);
/// Maps the `uri` of each input [ImportModel] or [ExportModel] to its
/// associated `.ng_deps.json` file, if one exists.
Future<Map<String, String>> _processNgImports(NgDepsModel model,
AssetReader reader, AssetId assetId, UrlResolver resolver) async {
final importsAndExports = new List.from(model.imports)..addAll(model.exports);
final retVal = <String, String>{};
final assetUri = toAssetUri(assetId);
return Future
.wait(
importsAndExports.where(_isNotDartDirective).map((dynamic directive) {
// The uri of the import or export with .dart replaced with .ng_meta.json.
// This is the json file containing Angular 2 codegen info, if one exists.
var linkedJsonUri =
resolver.resolve(assetUri, toMetaExtension(directive.uri));
return reader.hasInput(fromUri(linkedJsonUri)).then((hasInput) {
if (hasInput) {
retVal[directive.uri] = linkedJsonUri;
}
}, onError: (err, stack) {
logger.warning('Error while looking for $linkedJsonUri. '
'Message: $err\n'
'Stack: $stack');
});
}))
.then((_) => retVal);
}

View File

@ -0,0 +1,88 @@
library angular2.transform.directive_metadata_linker.linker;
import 'dart:async';
import 'dart:convert';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/ng_meta.dart';
import 'package:angular2/src/transform/common/url_resolver.dart';
import 'package:barback/barback.dart';
import 'ng_deps_linker.dart';
/// Returns [NgMeta] associated with [entryPoint] combined with the [NgMeta] of
/// all files `export`ed from the original file.
///
/// This includes entries for every `Directive`-annotated class and
/// constants that match the directive-aliases pattern.
///
/// There are entries for each of these which is visible from a file importing
/// the original .dart file that produced `entryPoint`. That is, this includes
/// all `Directive` annotated public classes in that file, all `DirectiveAlias`
/// annotated public variables, and any of those entries which are visible from
/// files which the .dart file `export`ed.
///
/// Returns an empty [NgMeta] if there are no `Directive`-annotated classes or
/// `DirectiveAlias` annotated constants in `entryPoint`.
Future<NgMeta> linkDirectiveMetadata(
AssetReader reader, AssetId entryPoint) async {
var ngMeta = await _readNgMeta(reader, entryPoint);
if (ngMeta == null || ngMeta.isEmpty) return null;
await Future.wait([
linkNgDeps(ngMeta.ngDeps, reader, entryPoint, _urlResolver),
_linkDirectiveMetadataRecursive(
ngMeta, reader, entryPoint, new Set<String>())
]);
return ngMeta;
}
Future<NgMeta> _readNgMeta(AssetReader reader, AssetId ngMetaAssetId) async {
if (!(await reader.hasInput(ngMetaAssetId))) return null;
var ngMetaJson = await reader.readAsString(ngMetaAssetId);
if (ngMetaJson == null || ngMetaJson.isEmpty) return null;
return new NgMeta.fromJson(JSON.decode(ngMetaJson));
}
final _urlResolver = const TransformerUrlResolver();
Future<NgMeta> _linkDirectiveMetadataRecursive(NgMeta ngMeta,
AssetReader reader, AssetId assetId, Set<String> seen) async {
if (ngMeta == null ||
ngMeta.ngDeps == null ||
ngMeta.ngDeps.exports == null) {
return ngMeta;
}
var assetUri = toAssetUri(assetId);
return Future
.wait(ngMeta.ngDeps.exports
.where((export) => !isDartCoreUri(export.uri))
.map((export) =>
_urlResolver.resolve(assetUri, toMetaExtension(export.uri)))
.where((uri) => !seen.contains(uri))
.map((uri) async {
seen.add(uri);
try {
final exportAssetId = fromUri(uri);
if (await reader.hasInput(exportAssetId)) {
var exportNgMetaJson = await reader.readAsString(exportAssetId);
if (exportNgMetaJson == null) return null;
var exportNgMeta = new NgMeta.fromJson(JSON.decode(exportNgMetaJson));
await _linkDirectiveMetadataRecursive(
exportNgMeta, reader, exportAssetId, seen);
if (exportNgMeta != null) {
ngMeta.addAll(exportNgMeta);
}
}
} catch (err, st) {
// Log and continue.
logger.warning('Failed to fetch $uri. Message: $err.\n$st');
}
}))
.then((_) => ngMeta);
}

View File

@ -4,11 +4,13 @@ import 'dart:async';
import 'dart:convert';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/code/ng_deps_code.dart';
import 'package:angular2/src/transform/common/formatter.dart';
import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart';
import 'package:barback/barback.dart';
import 'linker.dart';
import 'ng_meta_linker.dart';
/// Transformer responsible for processing .ng_meta.json files created by
/// {@link DirectiveProcessor} and "linking" them.
@ -31,6 +33,7 @@ class DirectiveMetadataLinker extends Transformer
// incorrectly determine what assets are available in this phase.
// transform.consumePrimary();
transform.declareOutput(transform.primaryId);
transform.declareOutput(_depsAssetId(transform.primaryId));
}
@override
@ -42,15 +45,32 @@ class DirectiveMetadataLinker extends Transformer
new AssetReader.fromTransform(transform), primaryId).then((ngMeta) {
// See above
// transform.consumePrimary();
if (ngMeta != null && !ngMeta.isEmpty) {
transform.addOutput(new Asset.fromString(
primaryId, _encoder.convert(ngMeta.toJson())));
} else {
// Not outputting an asset could confuse barback, so output an
// empty one.
transform.addOutput(transform.primaryInput);
if (ngMeta != null) {
if (!ngMeta.types.isEmpty || !ngMeta.aliases.isEmpty) {
transform.addOutput(new Asset.fromString(
primaryId, _encoder.convert(ngMeta.toJson(withNgDeps: false))));
} else {
// Not outputting an asset could confuse barback.
transform.addOutput(new Asset.fromString(primaryId, ''));
}
var depsAssetId = _depsAssetId(primaryId);
if (!ngMeta.isNgDepsEmpty) {
var buf = new StringBuffer();
var writer = new NgDepsWriter(buf);
writer.writeNgDepsModel(ngMeta.ngDeps);
var formattedCode =
formatter.format(buf.toString(), uri: depsAssetId.path);
transform
.addOutput(new Asset.fromString(depsAssetId, formattedCode));
} else {
transform.addOutput(new Asset.fromString(depsAssetId, ''));
}
}
});
});
}
}
AssetId _depsAssetId(AssetId primaryId) =>
new AssetId(primaryId.package, toDepsExtension(primaryId.path));

View File

@ -9,7 +9,6 @@ import 'package:angular2/src/transform/common/code/ng_deps_code.dart';
import 'package:angular2/src/transform/common/directive_metadata_reader.dart';
import 'package:angular2/src/transform/common/interface_matcher.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/model/ng_deps_model.pb.dart';
import 'package:angular2/src/transform/common/ng_compiler.dart';
import 'package:angular2/src/transform/common/ng_meta.dart';
import 'package:barback/barback.dart' show AssetId;
@ -17,26 +16,20 @@ import 'package:angular2/src/core/compiler/template_compiler.dart';
import 'inliner.dart';
/// Generates a file registering all Angular 2 `Directive`s found in `code` in
/// ngDeps format [TODO(kegluneq): documentation reference needed]. `assetId` is
/// the id of the asset containing `code`.
///
/// If no Angular 2 `Directive`s are found in `code`, returns the empty
/// string unless `forceGenerate` is true, in which case an empty ngDeps
/// file is created.
Future<NgDepsModel> createNgDeps(AssetReader reader, AssetId assetId,
AnnotationMatcher annotationMatcher, NgMeta ngMeta) async {
/// Generates an instance of [NgMeta] describing the file at `assetId`.
Future<NgMeta> createNgDeps(AssetReader reader, AssetId assetId,
AnnotationMatcher annotationMatcher) async {
// TODO(kegluneq): Shortcut if we can determine that there are no
// [Directive]s present, taking into account `export`s.
var codeWithParts = await inlineParts(reader, assetId);
if (codeWithParts == null || codeWithParts.isEmpty) return null;
var parsedCode =
parseCompilationUnit(codeWithParts, name: '${assetId.path} and parts');
var ngDepsVisitor = new NgDepsVisitor(assetId, annotationMatcher);
parsedCode.accept(ngDepsVisitor);
var ngDepsModel = ngDepsVisitor.model;
var ngMeta = new NgMeta(ngDeps: ngDepsVisitor.model);
var templateCompiler = createTemplateCompiler(reader);
var ngMetaVisitor = new _NgMetaVisitor(
@ -44,21 +37,9 @@ Future<NgDepsModel> createNgDeps(AssetReader reader, AssetId assetId,
parsedCode.accept(ngMetaVisitor);
await ngMetaVisitor.whenDone();
// If this file imports only dart: libraries and does not define any
// reflectables of its own, it doesn't need a .ng_deps.dart file.
if (ngDepsModel.reflectables == null || ngDepsModel.reflectables.isEmpty) {
if (ngDepsModel.imports.every(_isDartImport) &&
ngDepsModel.exports.every(_isDartImport)) {
return null;
}
}
return ngDepsModel;
return ngMeta;
}
// `model` can be an [ImportModel] or [ExportModel].
bool _isDartImport(dynamic model) => model.uri.startsWith('dart:');
// TODO(kegluneq): Allow the caller to provide an InterfaceMatcher.
final _interfaceMatcher = new InterfaceMatcher();
@ -96,12 +77,6 @@ class _NgMetaVisitor extends Object with SimpleAstVisitor<Object> {
return node.declarations.accept(this);
}
@override
Object visitExportDirective(ExportDirective node) {
ngMeta.exports.add(stringLiteralToString(node.uri));
return null;
}
@override
Object visitClassDeclaration(ClassDeclaration node) {
_normalizations.add(_reader

View File

@ -8,7 +8,6 @@ import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/options.dart';
import 'package:angular2/src/transform/common/ng_meta.dart';
import 'package:barback/barback.dart';
import 'rewriter.dart';
@ -37,7 +36,6 @@ class DirectiveProcessor extends Transformer implements DeclaringTransformer {
@override
declareOutputs(DeclaringTransform transform) {
transform.declareOutput(_ngMetaAssetId(transform.primaryId));
transform.declareOutput(_ngDepsAssetId(transform.primaryId));
}
@override
@ -46,20 +44,13 @@ class DirectiveProcessor extends Transformer implements DeclaringTransformer {
await log.initZoned(transform, () async {
var primaryId = transform.primaryInput.id;
var reader = new AssetReader.fromTransform(transform);
var ngMeta = new NgMeta.empty();
var ngDepsModel = await createNgDeps(
reader, primaryId, options.annotationMatcher, ngMeta);
// TODO(kegluneq): Combine NgDepsModel with NgMeta in a single .json file.
if (ngDepsModel != null) {
var ngDepsAssetId = _ngDepsAssetId(primaryId);
transform.addOutput(new Asset.fromString(
ngDepsAssetId, _encoder.convert(ngDepsModel.writeToJsonMap())));
}
var metaOutputId = _ngMetaAssetId(primaryId);
if (!ngMeta.isEmpty) {
transform.addOutput(new Asset.fromString(
metaOutputId, _encoder.convert(ngMeta.toJson())));
var ngMeta =
await createNgDeps(reader, primaryId, options.annotationMatcher);
if (ngMeta == null || ngMeta.isEmpty) {
return;
}
transform.addOutput(new Asset.fromString(
_ngMetaAssetId(primaryId), _encoder.convert(ngMeta.toJson())));
});
}
}
@ -68,8 +59,3 @@ AssetId _ngMetaAssetId(AssetId primaryInputId) {
return new AssetId(
primaryInputId.package, toMetaExtension(primaryInputId.path));
}
AssetId _ngDepsAssetId(AssetId primaryInputId) {
return new AssetId(
primaryInputId.package, toJsonExtension(primaryInputId.path));
}

View File

@ -30,7 +30,8 @@ class CompileDataResults {
NormalizedComponentWithViewDirectives> viewDefinitions;
final List<CompileDirectiveMetadata> directiveMetadatas;
CompileDataResults._(this.ngDeps, this.viewDefinitions, this.directiveMetadatas);
CompileDataResults._(
this.ngDeps, this.viewDefinitions, this.directiveMetadatas);
}
// TODO(kegluenq): Improve this test.
@ -86,9 +87,7 @@ class _CompileDataCreator {
Future<Map<AssetId, String>> _createImportAssetToPrefixMap() async {
var ngDeps = await ngDepsFuture;
var importAssetToPrefix = <AssetId, String>{
entryPoint: null
};
var importAssetToPrefix = <AssetId, String>{entryPoint: null};
for (ImportDirective node in ngDeps.imports) {
var uri = stringLiteralToString(node.uri);
@ -144,12 +143,15 @@ class _CompileDataCreator {
importAssetId.package, toMetaExtension(importAssetId.path));
if (await reader.hasInput(metaAssetId)) {
try {
var json = JSON.decode(await reader.readAsString(metaAssetId));
var newMetadata = new NgMeta.fromJson(json);
if (importAssetId == entryPoint) {
this.directiveMetadatas.addAll(newMetadata.types.values);
var jsonString = await reader.readAsString(metaAssetId);
if (jsonString != null && jsonString.isNotEmpty) {
var json = JSON.decode(jsonString);
var newMetadata = new NgMeta.fromJson(json);
if (importAssetId == entryPoint) {
this.directiveMetadatas.addAll(newMetadata.types.values);
}
ngMeta.addAll(newMetadata);
}
ngMeta.addAll(newMetadata);
} catch (ex, stackTrace) {
logger.warning('Failed to decode: $ex, $stackTrace',
asset: metaAssetId);

View File

@ -4,7 +4,6 @@ import 'package:barback/barback.dart';
import 'package:dart_style/dart_style.dart';
import 'deferred_rewriter/transformer.dart';
import 'directive_linker/transformer.dart';
import 'directive_metadata_linker/transformer.dart';
import 'directive_processor/transformer.dart';
import 'bind_generator/transformer.dart';
@ -30,14 +29,8 @@ class AngularTransformerGroup extends TransformerGroup {
[new ReflectionRemover(options)],
[new DirectiveProcessor(options)]
];
phases.addAll(new List.generate(
options.optimizationPhases, (_) => [new EmptyNgDepsRemover()]));
phases.addAll([
[
new DeferredRewriter(options),
new DirectiveLinker(),
new DirectiveMetadataLinker()
],
[new DeferredRewriter(options), new DirectiveMetadataLinker()],
[new BindGenerator(options)],
[new TemplateCompiler(options)],
[new StylesheetCompiler()],