add initWebComponents
R=sigmund@google.com
Review URL: https://codereview.chromium.org//1026153002
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6de608f..a46dbea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,21 @@
+#### 0.11.1
+ * Added `initWebComponents` function which performs html import aware
+ initialization of an application. This is done by crawling all imported
+ documents for dart script tags and initializing them. Any applications using
+ this package should switch to this method instead of calling `run` from the
+ `initialize` package directly.
+ * You may also now just export `package:web_components/init.dart` to
+ initialize your app, and then stick your startup code inside a method marked
+ with `@initMethod`, for instance:
+
+ library my_app;
+ export 'package:web_components/init.dart';
+
+ @initMethod
+ void startup() {
+ // custom app code here.
+ }
+
#### 0.11.0
* Add `bindingStartDelimiters` option to the `ImportInlinerTransformer`. Any
urls which contain any of the supplied delimiters before the first `/` will
diff --git a/lib/build/mirrors_remover.dart b/lib/build/mirrors_remover.dart
new file mode 100644
index 0000000..4cb8862
--- /dev/null
+++ b/lib/build/mirrors_remover.dart
@@ -0,0 +1,25 @@
+// 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.mirrors_remover;
+
+import 'dart:async';
+import 'package:barback/barback.dart';
+
+/// Removes `mirror_initializer.dart` and replaces it with
+/// `static_initializer.dart`.
+class MirrorsRemoverTransformer extends Transformer {
+ MirrorsRemoverTransformer();
+ MirrorsRemoverTransformer.asPlugin(BarbackSettings settings);
+
+ bool isPrimary(AssetId id) => id.path == 'lib/src/init.dart';
+
+ @override
+ Future apply(Transform transform) async {
+ String source = await transform.primaryInput.readAsString();
+ source = source.replaceFirst(
+ 'mirror_initializer.dart', 'static_initializer.dart');
+ transform.addOutput(
+ new Asset.fromString(transform.primaryInput.id, source));
+ }
+}
diff --git a/lib/init.dart b/lib/init.dart
new file mode 100644
index 0000000..1ebb141
--- /dev/null
+++ b/lib/init.dart
@@ -0,0 +1,9 @@
+// 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.init;
+
+import 'dart:async';
+import 'src/init.dart';
+
+Future main() => initWebComponents();
diff --git a/lib/src/init.dart b/lib/src/init.dart
new file mode 100644
index 0000000..512134d
--- /dev/null
+++ b/lib/src/init.dart
@@ -0,0 +1,29 @@
+// 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.src.init;
+
+import 'dart:async';
+import 'package:initialize/initialize.dart' show InitializerFilter;
+import 'package:web_components/web_components.dart';
+import 'mirror_initializer.dart' as init;
+
+/// Performs html import aware initialization by crawling all imported documents
+/// and initializing any script tags which appear in them.
+///
+/// By default, this will run all [HtmlImport] initializers, followed in a 2nd
+/// phase by all [CustomElement] and [CustomElementProxy] initializers. Then,
+/// unless [initAll] is [false], it will run all remaining initializers.
+///
+/// If a [typeFilter] or [customFilter] are supplied, only one phase is ran
+/// with the supplied filters.
+Future initWebComponents({List<Type> typeFilter, InitializerFilter customFilter,
+ bool initAll: true}) {
+ if (typeFilter != null || customFilter != null) {
+ return init.run(typeFilter: typeFilter, customFilter: customFilter);
+ } else {
+ return init.run(typeFilter: [HtmlImport])
+ .then((_) => init.run(typeFilter: [CustomElement, CustomElementProxy]))
+ .then((_) => initAll ? init.run() : null);
+ }
+}
diff --git a/lib/src/mirror_initializer.dart b/lib/src/mirror_initializer.dart
new file mode 100644
index 0000000..652031f
--- /dev/null
+++ b/lib/src/mirror_initializer.dart
@@ -0,0 +1,130 @@
+// Copyright (c) 2014, 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.
+
+/// Contains logic to initialize web_components apps during development. This
+/// implementation uses dart:mirrors to load each library as they are discovered
+/// through HTML imports. This is only meant to be used during development in
+/// dartium, and the web_components transformers replace this implementation
+/// for deployment.
+library web_components.src.mirror_initializer;
+
+import 'dart:async';
+import 'dart:collection' show LinkedHashMap;
+import 'dart:mirrors';
+import 'dart:html';
+import 'package:initialize/initialize.dart' as init;
+
+Future run({List<Type> typeFilter, init.InitializerFilter customFilter}) async {
+ var libraryUris = _discoverLibrariesToLoad(document, window.location.href)
+ .map(Uri.parse);
+
+ for (var uri in libraryUris) {
+ await init.run(
+ typeFilter: typeFilter, customFilter: customFilter, from: uri);
+ }
+ return null;
+}
+
+/// Walks the HTML import structure to discover all script tags that are
+/// implicitly loaded. This code is only used in Dartium and should only be
+/// called after all HTML imports are resolved. Polymer ensures this by asking
+/// users to put their Dart script tags after all HTML imports (this is checked
+/// by the linter, and Dartium will otherwise show an error message).
+Iterable<_ScriptInfo> _discoverScripts(
+ Document doc, String baseUri, [_State state]) {
+ if (state == null) state = new _State();
+ if (doc == null) {
+ print('warning: $baseUri not found.');
+ return state.scripts.values;
+ }
+ if (!state.seen.add(doc)) return state.scripts.values;
+
+ for (var node in doc.querySelectorAll('script,link[rel="import"]')) {
+ if (node is LinkElement) {
+ _discoverScripts(node.import, node.href, state);
+ } else if (node is ScriptElement && node.type == 'application/dart') {
+ var info = _scriptInfoFor(node, baseUri);
+ if (state.scripts.containsKey(info.resolvedUrl)) {
+ // TODO(jakemac): Move this to a web_components uri.
+ print('warning: Script `${info.resolvedUrl}` included more than once. '
+ 'See http://goo.gl/5HPeuP#polymer_44 for more details.');
+ } else {
+ state.scripts[info.resolvedUrl] = info;
+ }
+ }
+ }
+ return state.scripts.values;
+}
+
+/// Internal state used in [_discoverScripts].
+class _State {
+ /// Documents that we have visited thus far.
+ final Set<Document> seen = new Set();
+
+ /// Scripts that have been discovered, in tree order.
+ final LinkedHashMap<String, _ScriptInfo> scripts = {};
+}
+
+/// Holds information about a Dart script tag.
+class _ScriptInfo {
+ /// The original URL seen in the tag fully resolved.
+ final String resolvedUrl;
+
+ /// Whether it seems to be a 'package:' URL (starts with the package-root).
+ bool get isPackage => packageUrl != null;
+
+ /// The equivalent 'package:' URL, if any.
+ final String packageUrl;
+
+ _ScriptInfo(this.resolvedUrl, {this.packageUrl});
+}
+
+
+// TODO(sigmund): explore other (cheaper) ways to resolve URIs relative to the
+// root library (see dartbug.com/12612)
+final _rootUri = currentMirrorSystem().isolate.rootLibrary.uri;
+
+/// Returns [_ScriptInfo] for [script] which was seen in [baseUri].
+_ScriptInfo _scriptInfoFor(script, baseUri) {
+ var uriString = script.src;
+ if (uriString != '') {
+ var uri = _rootUri.resolve(uriString);
+ if (!_isHttpStylePackageUrl(uri)) return new _ScriptInfo('$uri');
+ // Use package: urls if available. This rule here is more permissive than
+ // how we translate urls in polymer-build, but we expect Dartium to limit
+ // the cases where there are differences. The polymer-build issues an error
+ // when using packages/ inside lib without properly stepping out all the way
+ // to the packages folder. If users don't create symlinks in the source
+ // tree, then Dartium will also complain because it won't find the file seen
+ // in an HTML import.
+ var packagePath = uri.path.substring(
+ uri.path.lastIndexOf('packages/') + 'packages/'.length);
+ return new _ScriptInfo('$uri', packageUrl: 'package:$packagePath');
+ }
+
+ // Even in the case of inline scripts its ok to just use the baseUri since
+ // there can only be one per page.
+ return new _ScriptInfo(baseUri);
+}
+
+/// Whether [uri] is an http URI that contains a 'packages' segment, and
+/// therefore could be converted into a 'package:' URI.
+bool _isHttpStylePackageUrl(Uri uri) {
+ var uriPath = uri.path;
+ return uri.scheme == _rootUri.scheme &&
+ // Don't process cross-domain uris.
+ uri.authority == _rootUri.authority &&
+ uriPath.endsWith('.dart') &&
+ (uriPath.contains('/packages/') || uriPath.startsWith('packages/'));
+}
+
+Iterable<String> _discoverLibrariesToLoad(Document doc, String baseUri) =>
+ _discoverScripts(doc, baseUri).map(
+ (info) => _packageUrlExists(info) ? info.packageUrl : info.resolvedUrl);
+
+/// All libraries in the current isolate.
+final _libs = currentMirrorSystem().libraries;
+
+bool _packageUrlExists(_ScriptInfo info) =>
+ info.isPackage && _libs[Uri.parse(info.packageUrl)] != null;
diff --git a/lib/src/static_initializer.dart b/lib/src/static_initializer.dart
new file mode 100644
index 0000000..dd23f8a
--- /dev/null
+++ b/lib/src/static_initializer.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2014, 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.
+
+/// Static logic for initializing web_components. This assumes the entire app
+/// is reachable from the entry point.
+library web_components.src.static_initializer;
+
+import 'dart:async';
+import 'package:initialize/initialize.dart' as init;
+
+Future run({List<Type> typeFilter, init.InitializerFilter customFilter}) =>
+ init.run(typeFilter: typeFilter, customFilter: customFilter);
diff --git a/lib/web_components.dart b/lib/web_components.dart
index 379d04e..9ad6d84 100644
--- a/lib/web_components.dart
+++ b/lib/web_components.dart
@@ -4,5 +4,6 @@
library web_components;
export 'src/custom_element.dart';
+export 'src/init.dart';
export 'custom_element_proxy.dart';
export 'html_import_annotation.dart';
diff --git a/pubspec.yaml b/pubspec.yaml
index 9133a77..4cc22dd 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: web_components
-version: 0.11.0
+version: 0.11.1
author: Polymer.dart Authors <web-ui-dev@dartlang.org>
homepage: https://www.dartlang.org/polymer-dart/
description: >
@@ -14,18 +14,21 @@
barback: '>=0.14.2 <0.16.0'
code_transformers: '^0.2.7'
html5lib: '^0.12.0'
- initialize: '^0.5.0+1'
+ initialize: '^0.6.0'
path: '^1.3.0'
dev_dependencies:
unittest: '^0.11.0'
browser: '^0.10.0'
transformers:
+- web_components/build/mirrors_remover:
+ $include: 'lib/src/init.dart'
- web_components:
$include: '**/*_test.html'
entry_points:
- test/custom_element_test.html
- test/custom_element_proxy_test.html
- test/html_import_annotation_test.html
+ - test/init_web_components_test.html
environment:
sdk: ">=1.9.0-dev.7.1 <2.0.0"
diff --git a/test/build/mirrors_remover_test.dart b/test/build/mirrors_remover_test.dart
new file mode 100644
index 0000000..ab9fa08
--- /dev/null
+++ b/test/build/mirrors_remover_test.dart
@@ -0,0 +1,34 @@
+// 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.test.build.mirrors_remover_test;
+
+import 'package:code_transformers/tests.dart';
+import 'package:web_components/build/mirrors_remover.dart';
+import 'package:unittest/compact_vm_config.dart';
+import 'package:unittest/unittest.dart';
+
+main() {
+ useCompactVMConfiguration();
+
+ var transformer = new MirrorsRemoverTransformer();
+ var phases = [[transformer]];
+
+ testPhases('basic', phases, {
+ 'a|lib/src/init.dart': '''
+ libary web_components.init;
+
+ import 'src/mirror_initializer.dart';
+
+ foo() {}
+ ''',
+ }, {
+ 'a|lib/src/init.dart': '''
+ libary web_components.init;
+
+ import 'src/static_initializer.dart';
+
+ foo() {}
+ ''',
+ }, []);
+}
diff --git a/test/custom_element_proxy_test.dart b/test/custom_element_proxy_test.dart
index 3437440..5fa11e0 100644
--- a/test/custom_element_proxy_test.dart
+++ b/test/custom_element_proxy_test.dart
@@ -6,10 +6,9 @@
import 'dart:async';
import 'dart:html';
import 'dart:js';
-import 'package:initialize/initialize.dart' as init;
import 'package:unittest/html_config.dart';
import 'package:unittest/unittest.dart';
-import 'package:web_components/custom_element_proxy.dart';
+import 'package:web_components/web_components.dart';
@CustomElementProxy('basic-element')
class BasicElement extends HtmlElement {
@@ -34,7 +33,7 @@
main() {
useHtmlConfiguration();
- init.run().then((_) {
+ initWebComponents().then((_) {
var container = querySelector('#container') as DivElement;
tearDown(() {
diff --git a/test/custom_element_test.dart b/test/custom_element_test.dart
index 2418def..641c8c9 100644
--- a/test/custom_element_test.dart
+++ b/test/custom_element_test.dart
@@ -5,7 +5,6 @@
import 'dart:async';
import 'dart:html';
-import 'package:initialize/initialize.dart' as init;
import 'package:unittest/html_config.dart';
import 'package:unittest/unittest.dart';
import 'package:web_components/web_components.dart';
@@ -34,7 +33,7 @@
main() {
useHtmlConfiguration();
- init.run().then((_) {
+ initWebComponents().then((_) {
var container = querySelector('#container') as DivElement;
setUp(() {
diff --git a/test/deps/a.dart b/test/deps/a.dart
new file mode 100644
index 0000000..8e2d6a1
--- /dev/null
+++ b/test/deps/a.dart
@@ -0,0 +1,7 @@
+// 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.
+@initializeTracker
+library web_components.test.deps.a;
+
+import 'package:initialize/src/initialize_tracker.dart';
diff --git a/test/deps/a.html b/test/deps/a.html
new file mode 100644
index 0000000..7636d42
--- /dev/null
+++ b/test/deps/a.html
@@ -0,0 +1,3 @@
+<link rel="import" href="b.html">
+<link rel="import" href="c.html">
+<script type="application/dart" src="a.dart"></script>
diff --git a/test/deps/b.dart b/test/deps/b.dart
new file mode 100644
index 0000000..63b8dfa
--- /dev/null
+++ b/test/deps/b.dart
@@ -0,0 +1,7 @@
+// 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.
+@initializeTracker
+library web_components.test.deps.b;
+
+import 'package:initialize/src/initialize_tracker.dart';
diff --git a/test/deps/b.html b/test/deps/b.html
new file mode 100644
index 0000000..14abd88
--- /dev/null
+++ b/test/deps/b.html
@@ -0,0 +1 @@
+<script type="application/dart" src="b.dart"></script>
diff --git a/test/deps/c.html b/test/deps/c.html
new file mode 100644
index 0000000..e312085
--- /dev/null
+++ b/test/deps/c.html
@@ -0,0 +1,6 @@
+<script type="application/dart">
+ @initializeTracker
+ library web_components.test.deps.c;
+
+ import 'package:initialize/src/initialize_tracker.dart';
+</script>
diff --git a/test/html_import_annotation_test.dart b/test/html_import_annotation_test.dart
index 09a3ffd..6b81a58 100644
--- a/test/html_import_annotation_test.dart
+++ b/test/html_import_annotation_test.dart
@@ -7,10 +7,9 @@
library web_components.test.html_import_annotation;
import 'dart:html';
-import 'package:initialize/initialize.dart' as init;
import 'package:unittest/html_config.dart';
import 'package:unittest/unittest.dart';
-import 'package:web_components/html_import_annotation.dart';
+import 'package:web_components/web_components.dart';
import 'foo/bar.dart';
const String importPath = 'my_import.html';
@@ -19,7 +18,7 @@
useHtmlConfiguration();
test('adds import to head', () {
- return init.run().then((_) {
+ return initWebComponents().then((_) {
var my_import = document.head.querySelector('link[href="$importPath"]');
expect(my_import, isNotNull);
expect(my_import.import.body.text, 'Hello world!\n');
diff --git a/test/init_web_components_test.dart b/test/init_web_components_test.dart
new file mode 100644
index 0000000..15279e5
--- /dev/null
+++ b/test/init_web_components_test.dart
@@ -0,0 +1,33 @@
+// 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.
+@initializeTracker
+library web_components.test.init_web_components_test;
+
+import 'package:unittest/html_config.dart';
+import 'package:unittest/unittest.dart';
+import 'package:initialize/initialize.dart' show LibraryIdentifier;
+import 'package:initialize/src/initialize_tracker.dart';
+import 'package:web_components/web_components.dart';
+
+const String importPath = 'my_import.html';
+
+main() {
+ useHtmlConfiguration();
+
+ test('can initialize scripts from html imports', () {
+ return initWebComponents().then((_) {
+ var expectedInitializers = [
+ const LibraryIdentifier(
+ #web_components.test.deps.b, null, 'deps/b.dart'),
+ const LibraryIdentifier(
+ #web_components.test.deps.c, null, 'deps/c.html'),
+ const LibraryIdentifier(
+ #web_components.test.deps.a, null, 'deps/a.dart'),
+ const LibraryIdentifier(#web_components.test.init_web_components_test,
+ null, 'init_web_components_test.dart'),
+ ];
+ expect(InitializeTracker.seen, expectedInitializers);
+ });
+ });
+}
diff --git a/test/init_web_components_test.html b/test/init_web_components_test.html
new file mode 100644
index 0000000..d418e6e
--- /dev/null
+++ b/test/init_web_components_test.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="dart.unittest" content="full-stack-traces">
+ <title> init_web_components test </title>
+ <style>
+ .unittest-table { font-family:monospace; border:1px; }
+ .unittest-pass { background: #6b3;}
+ .unittest-fail { background: #d55;}
+ .unittest-error { background: #a11;}
+ </style>
+ <script src="/packages/web_components/webcomponents.js"></script>
+ <script src="/packages/web_components/interop_support.js"></script>
+ <script src="/packages/web_components/dart_support.js"></script>
+ <link rel="import" href="deps/a.html">
+</head>
+<body>
+ <h1> Running init_web_components test </h1>
+
+ <script type="text/javascript"
+ src="/root_dart/tools/testing/dart/test_controller.js"></script>
+ <script type="application/dart" src="init_web_components_test.dart"></script>
+ <script type="text/javascript" src="/packages/browser/dart.js"></script>
+</body>
+</html>