Skip to content

Instantly share code, notes, and snippets.

@matanlurey
Last active July 24, 2017 00:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matanlurey/0fec1ac15a7a80c898870546123b70b3 to your computer and use it in GitHub Desktop.
Save matanlurey/0fec1ac15a7a80c898870546123b70b3 to your computer and use it in GitHub Desktop.

Background

Origin

AngularDart (v1) had a simple meta programming story: it relied on dart:mirrors at development time (and in certain other aspects, like unit and component tests), and used a simple form of code generation for production, with an interface approximately as follows:

// interface.dart

abstract class MetadataExtractor {
  Iterable extractMetadata(Type directiveOrInjectable);
}

// dynamic.dart

import ‘dart:mirrors’;
 
class DynamicMetadataExtractor {
  Iterable extractMetadata(Type directiveOrInjectable) {
    reflectType(directiveOrInjectable);
    // ...
  }
}


// static.dart

class StaticMetadataExtractor {
  factory StaticMetadataExtractor(Map<Type, Iterable> metadata) => … 
}

That meant that AngularDart (v1) had a whole-program compile-step to generate StaticMetadataExtractor, for example, the following might have been generated for a small application:

final staticMetadata = {
  FooComponent: [
    new Component(
      selector: 'foo',
      … 
    ),
  ],

  // For every @Injectable(), @Directive(), and @Component() at minimum.
};

Today

To allow modular [re]-compilation, AngularDart (v2) was built approximately the same way as AngularDart (v1), but instead "shards" the creation of this map of relevant metadata (called reflector now) across generated files. A single .template.dart file shadows every .dart file the Angular compiler runs on. So for example, this is generated:

void initReflector() {
  reflector.registerType(Foo, new ComponentFactory(...));
}

And if you import any files, we detect if a *.template.dart *file exists for it, and links together:

// foo.dart
 
import ‘package:angular2/angular2.dart’;
import ‘bar.dart’;
 
@Component(...)
class Foo {}

// foo.template.dart
 
import ‘package:angular2/angular2.template.dart’ as _i1;
import ‘bar.template.dart ‘as _i2;
 
void initReflector() {
  reflector.registerType(Foo, new ComponentFactory(...));
  _i1.initReflector();
  _i2.initReflector();
}

Issues

Today's approach causes a number of issues.

Seriously hurts tree-shakability

Consider the following import:

// app.dart
 
import ‘package:angular_components/angular_components.dart’;
 
// app.template.dart
 
import ‘package:angular_components/angular_components.template.dart’ as _i1;
 
void initReflector() {
  _i1.initReflector();
}

In this case, we will register type and component information for every component, directive, pipe, and injectable in pkg/angular_components, even if you only use a single component.

@jodinathan
Copy link

Hi Matan.
Is the new compiler preventing this issues?
Or is there a advanced guideline to prevent unused code to be compiled?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment