blob: c223fc94ddb8251d79b2aebb5fb040a6390e0b3d [file] [log] [blame] [view]
Sky Module System
=================
This document describes the Sky module system.
Overview
--------
The Sky module system is based on the ``import`` element. In its
most basic form, you import a module as follows:
```html
<import src="path/to/module.sky" />
```
As these ``import`` elements are inserted into a document, the
document's list of outstanding dependencies grows. When an imported
module completes, it is removed from the document's list of
outstanding dependencies.
Before executing script or inserting an element that is not already
registered, the parser waits until the list of outstanding
dependencies is empty. After the parser has finished parsing, the
document waits until its list of outstanding dependencies is empty
before the module it represents is marked complete.
Module API
----------
Within a script in a module, the ``module`` identifier is bound to
the ``Module`` object that represents the module.
### Exporting values ###
A module can export a value by assigning the ``exports`` property of
its ``Module`` object. By default, the ``exports`` property of a
``Module`` is an empty Object. Properties can be added to the object,
or, it can be set to an entirely different object; for example, it
could be set to the module's ``Document`` itself, in case the point of
the module is to expose some ``template`` elements.
### Exporting element definitions ###
When importing a module into another, Sky runs the following steps:
- let export be the imported module's ``exports`` value
- try to import export
- if that fails:
- try to import each property of export
"Try to import" a value means to run the following steps:
- if the value is an element constructor (generated by
``registerElement()``), call this importer module's
``registerElement()`` with the value
### IDL ###
```javascript
dictionary InternalElementOptions {
String tagName;
Boolean shadow = false;
Object prototype = Element;
}
interface InternalElementConstructorWithoutShadow {
constructor (Module hostModule);
attribute String tagName;
}
interface InternalElementConstructorWithShadow {
constructor (Module hostModule);
attribute String tagName;
attribute Boolean shadow;
}
typedef ElementRegistrationOptions (InternalElementOptions or
InternalElementConstructorWithoutShadow or
InternalElementConstructorWithShadow);
abstract class AbstractModule : EventTarget {
readonly attribute Document document; // O(1) // the Document of the module or application
Promise<any> import(String url); // O(Yikes) // returns the module's exports
private Array<Module> getImports(); O(N) // returns the Module objects of all the imported modules
readonly attribute String url;
ElementConstructor registerElement(Object options); // O(1)
// if you call registerElement() with an object that was created by
// registerElement(), it just returns the object after registering it,
// rather than creating a new constructor
// otherwise, it proceeds as follows:
// - if options is a Function (i.e. it is either an
// InternalElementConstructorWithoutShadow object or an
// InternalElementConstructorWithShadow object), then let
// constructor be that function, and let prototype be that
// functions's prototype; otherwise, let constructor be a no-op
// function and let prototype be the prototype property of the
// object passed in (the InternalElementOptions; prototype
// defaults to Element).
// - let shadow be option's shadow property's value coerced to a
// boolean, if the property is present, or else the value false.
// - let tagName be option's tagName property's value.
// - create a new Function that acts as if it had the signature of
// the constructors in the ElementConstructor interface, and that
// runs the follows steps when called:
// - throw if not called as a constructor
// - create an actual element object (the C++-backed object)
// called tagName, along with the specified attributes
// - initialise the shadow tree if shadow is true
// - call constructor, if it's not null, with the module
// within which the new element is being constructed as the
// argument
// - append all the specified children
// - mark that new Function as created by registerElement() so that
// it can be recognised if used as an argument to
// registerElement()
// - let that new Function's prototype be the aforementioned prototype
// - let that new Function have tagName and shadow properties set to
// the aforementioned tagName and shadow
// - register the new tagName with this constructor
// - return the new Function (which is, not coincidentally, an
// InternalElementConstructorWithShadow)
readonly attribute ScriptElement? currentScript; // O(1) // returns the <script> element currently being executed if any, and if it's in this module; else null
}
class Module : AbstractModule {
constructor (Application application, Document document, String url); // O(1)
readonly attribute Application application; // O(1)
attribute any exports; // O(1) // defaults to {}
}
class Application : AbstractModule {
constructor (Document document, String url); // O(1)
attribute String title; // O(1)
}
```
Naming modules
--------------
The ``as`` attribute on the ``import`` element binds a name to the
imported module:
```html
<import src="path/to/chocolate.sky" as="chocolate" />
```
The parser executes the contents of script elements inside a module as
if they were executed as follow:
```javascript
(new Function(name_1, ..., name_n, module, source_code)).call(
value_1, ..., value_n, source_module);
```
Where ``name_1`` through ``name_n`` are the names bound to the
various named imports in the script element's document,
``source_code`` is the text content of the script element,
``source_module`` is the ``Module`` object of the script element's
module, and ``value_1`` through ``value_n`` are the values
exported by the various named imports in the script element's
document.
When an import fails to load, the ``as`` name for the import gets
bound to ``undefined``.