Skip to content

Instantly share code, notes, and snippets.

@Swatinem
Last active August 29, 2015 14:23
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 Swatinem/29b4fbdce02618c2ec88 to your computer and use it in GitHub Desktop.
Save Swatinem/29b4fbdce02618c2ec88 to your computer and use it in GitHub Desktop.
yavd ideas 3

this follows the estree format

interface Fragment <: Node {
  type: "Fragment";
  body: [
    Component | Tag | Block | Conditional | Iteration |
    Expression // Any JS  Expression
 ]
}

interface Tag <: Node {
  type: "Tag";
  name: string; // might be a reference to a registered Component
  attributes: [ Attribute ];
  body: Fragment | null;
}

interface Attribute <: Node {
  type: "Attribute";
  name: string;
  value: Expression | null; // if null then this is a truthy boolean attribute
}

interface Block <: Node { // this will be used both for defining blocks and to use them
  type: "Block";
  name: string | null; // it will be the default block if null
  operation: "append" | "prepend" | null;
  body: Fragment | null;
}

interface Conditional <: Node {
  type: "Conditional";
  test: Expression;
  consequent: Fragment;
  alternate: Fragment | null;
}

interface Iteration <: Node {
  type: "Iteration";
  binding: Identifier;
  keybinding: Identifier | null;
  expression: Expression;
  body: Fragment;
  alternate: Fragment | null;
}

Any Identifier that has not been declared in the scope will be bound to the context. Otherwise I will have to figure out a way to just walk the AST and generate a few sets of functions:

  • beforeMount
  • afterMount
  • update
  • unmount
  • stringify for server-side rendering
  • template(doc) to generate a fragment inside a foreign document for importNodeing

DOM API

most of the component lifecycle functions take a Location that specifies where the component is to be mounted.

Location: Node | {
  parent: Node,
  before?: Node,
}
// Node will be automatically converted to {parent: Node, before: null} internally

The component tree is always walked back to front, and Nodes can simply be added via parent.insertBefore(…, before). Most dynamic functions will return an updated before marker, or just pass it through unmodified in case dynamic node did not have any children.

A component instance has the following lifecycle:

  • new a new instance is created
  • beforeMount(location, context) => before the instance will be mounted to a location with a context. the node is not guaranteed to be the final node that will be mounted into the document, because we might use foreign documentFragments that we can import.
  • afterMount(location) => before will be called after that instance is added to the real dom tree. do your event binding and so on here
  • update(location, context) => before will be called when the context changes
  • unmount(location) => before will be called when the node needs to be unmounted
  • beforeMount, afterMount, update, unmount can be called multiple times for a single instance, since it might be reused

I still need to figure out how to correctly handle child blocks and inlining here…

  • Fuck all this jsx bullshit…
  • Lets rewind it again and concentrate purely on codegen based on a generic AST. That AST can very well come from existing template languages, such as handlebars, jade, etc.
  • We will have support for plain tags with attributes and children, additionally:
  • support for basic interpolation / dynamic properties in attributes and dynamic text nodes
  • support for conditionals and iteration
  • support for custom tags (call them partials / mixins / whatever you like) with support for multiple blocks / transclusion
  • some things are inlineable, others are not
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment