-
Notifications
You must be signed in to change notification settings - Fork 33
Initialization from HTML
This document presents how to support static initialization of code loaded from HTML documents in Dart script tags and HTML imports. This document is part of a larger design to restructure how we consume custom elements in Dart.
Static initializers play an essential role with web components and custom element registration. We decided to design static initializers in two layers. A first layer is focused in how to support it in Dart standalone (see design doc). While a second layer (this document), describes how we support HTML documents, multiple Dart script tags, and HTML imports. The Dart static initialization design doc includes a more detailed discussion of our motivation behind this design.
We propose adding a new library html_init.dart
which exposes a top-level
function htmlInit.run()
. This function will crawl the HTML document of the
application and invoke the standalone initialization init.run()
on each
reachable Dart script tag (including HTML imports). HTML imports are visited in
a post-order fashion.
Note: the location of this library is still TBD. It could be in a new
package or part web_components
. We prefer to not put it in the initialize
package itself, to keep dependencies simple and keep initilize
independent of
HTML features.
To initialize the web application, we replace the static initialization method,
instead of using init.dart
, we use html_init.dart
:
import 'package:html_init/html_init.dart' as htmlInit;
main() => htmlInit.run();
We'll ensure that initialization is executed in post-order of HTML imports and Dart script tags. Within an HTML file, HTML imports and Dart script tags are visited in the order they appear in the HTML document.
As discussed in the static initializers design doc, we are guaranteed that
init.run
executes each initializer only once, even if we invoke it multiple
times. In addition, the new Dart execution semantics ensures that if two script
tags share a common package:
library, then it is canonicalized to be the same
library. These two features make it possible to build htmlInit.run
as a second
tier on top of the static initialization package, since we can guarantee that a
library will be initialized once, even if two separate Dart script tags share an
import to such library.
The html_init
implementation, however, still needs to pay attention to
canonicalize libraries imported via packages/
vs package:
imports. This is
important because HTML imports cannot use package:
imports, but our convention
is that they are treated the same.
In a separate proposal we discuss in detail how to add support for importing HTML documents from Dart files. Internally, they are implemented as static initializers themselves.
For a given library containing Dart-to-HTML imports, our goal is to:
- execute the initializers of the imported files before the initializers of the given library.
- execute initializers in a deterministic order. In our case we picked to process Dart-to-HTML before Dart imports.
These features in the static initialization package make it possible to achieve those goals:
-
Initializer filters: We'll first run initializers for the actual Dart-to-HTML imports, and then separately run all other initializers in the code. This two-phase process guarantees that Dart-To-HTML imports are processed before any Dart imports. Without filters, we can only guarantee that at development time Dart-to-HTML imports are visited after Dart imports, but at deployment time the order is reversed.
-
Directives are processed first: Dart-to-HTML imports can be expressed as annotations on library directives, this helps make it easier to ensure that Dart-To-HTML imports are processed before any other initializers within the library.
-
Asynchronous initializers: Dart-to-HTML imports dynamically inject HTML imports, and hence code might be loaded asynchronously. This is not supported by Dartium today, but once it is, support for asynchronous initializers would allow us to wait until the code is loaded and run the initializers on that newly loaded code before we return from the import initializer itself. This helps preserve the post-order semantics.
To support development in Dartium, the html_init.run
internally needs to:
- crawl HTML imports and discover Dart script tags
- for each Dart script tag (in post-order):
- Canonicalize the script URL in order to treat URLs containing "packages/" as if they were written with the "package:" scheme.
- Run the Dart-only initializer from it (init.run)
Turns out that the mirror loader library in polymer.dart
has an
implementation of most of the logic described here, except that it is tailored
to support @CustomTag
and @initMethod
, instead of invoking a generic
initialization library.
Except for a small piece, support for compilation for deployment is practically handled by transformers distributed in other parts of our system. In particular, there are 3 steps of transformation involved:
- combining script tags into a single program
- generating code to properly initialize the resulting program (provided by the static initialization package)
- inlining of HTML and Dart-to-HTML imports (provided by the
web_components
package)
Combining script tags (step 1) consists of basically:
- create a new bootstrap Dart script that imports all other script tags and invokes their main. Imports must be added in the same order as they are discovered by visiting the HTML import graph in post-order.
- remove all the original script tags, and in the location of the script tag in the main HTML page, inject a new script tag that loads the bootstrap Dart script created above.
This transformation is similar to what polymer.dart does today in
script_compactor.dart
.
After this step, all static initializers are in a single Dart script tag and the
call to html_init.run()
will simply initialize a single combined Dart program
(that is, it is equivalent to just calling init.run()
). The end result is that
all initializers are invoked in the same deterministic order as they were
invoked during development.
Finally, since we want to reduce the API surface to our users, we propose making
all these three transformers be run as part of one group transformer in the
web_components
package. So a user can simply write a pubspec with this
section:
transformers:
- web_components