Skip to content

Instantly share code, notes, and snippets.

@tbranyen
Last active March 14, 2017 00:16
Show Gist options
  • Save tbranyen/8ac84e3afd80b33188bbdf184a187175 to your computer and use it in GitHub Desktop.
Save tbranyen/8ac84e3afd80b33188bbdf184a187175 to your computer and use it in GitHub Desktop.
diffHTML 1.0 Middleware Specification + Documentation
// Documentation for authoring middleware in diffHTML v1.0
// Usage of the middleware we'll write below, I added it up top, since it's a
// single line:
diff.use(myFirstMiddleware());
// To make a diffHTML middleware, start by creating a normal function that
// accepts options. We don't need to configure our middleware yet, but we want
// to keep all middleware consistent with this convention.
functon myFirstMiddleware(options = {}) {
// diffHTML middleware is built around the concept of transaction render
// flows. A transaction flow is a series of tasks that are run in order, and
// each are passed a transaction object. By default diffHTML will execute a
// task for each step of the Virtual DOM rendering process. Middleware tasks
// are very similar to these built-in tasks, except that the task function is
// invoked at the start of every render, and if a function is returned, that
// function will be injected into the end of the render flow. This allows you
// to write middleware that can react to the lifecycle of a transaction.
// IMPORTANT: The function name is how it is displayed in the dev tools. It
// will convert camel-case into "My First Middleware", the `Task` is also
// required.
function myFirstMiddlewareTask(transaction) {
// When this function is invoked, a transaction has just started, so the
// transaction object will only contain initial properties:
//
// - domNode - The Node passed as the first argument to innerHTML/outerHTML
// - markup - The new tree to patch into the DOM, passed as the second arg
// - options - Any options passed to diffHTML, like `tasks` or `inner`
// - state - An object tied to the Node that persists across transactions
//
// A useful method on the transaction object is `abort`. If you return this
// method invoked, it will terminate the render flow immediately.
// If you return a function it will be called near the end of the render
// transaction flow along with all other registered middleware. This
// function is the "true" render task that gets injected into the flow.
return function() {
// By the time this function has been called, diffHTML will already have
// synchronized the new markup tree into the old tree and applied changes
// to the DOM (if no transitions were used). However, if transitions are
// used you will need to wait until all promises are complete, before
// working with the DOM.
//
// The transaction object at this point now contains two new properties:
//
// - patches - An object with all DOM tree, attribute, and value changes
// - promises - An array of promises that were attached from transitions
//
// While you may be tempted to simply Promise.all(promises) to know once
// rendering has truly completed, this would not catch cases where the
// transaction gets aborted, and may end up resolving after the
// transacton complete code has already run. Here is an official hook
// that can be used instead and is called once the transaction has
// actually ended.
//
// Simply add a function to set the hook. Don't worry about cleaning up
// these references. They are tied directly to the transaction instance,
// and are automatically removed after being called so they can be GC'd.
transaction.onceEnded(function() {
// The transaction has absolutely ended now, all transitions have
// completed, you can get the timestamp here to compare with one
// made when the task function first gets called to know how long
// rendering took.
});
};
}
// Optional: If your task needs internals from diffHTML, before any tasks
// run, this callback will fire synchronously after diffHTML internally
// registers the middleware. It is called with the full public diffHTML API,
myFirstMiddlewareTask.subscribe = function(diff) {
// If you need to connect to a web worker, devtools, or anything else that
// lasts the lifetime of the code, gets put inside this function.
};
// Optional: If your task is removed and has associated state cleanup, handle
// that here. It is called with the full public dsffHTML API,
myFirstMiddlewareTask.unsubscribe = function(diff) {
// If this middleware could get re-attached, the above subscribe will be
// called again, so make sure that you can create a workable lifecycle of
// startup and teardown. Don't make the teardown impossible to bring back
// up.
};
return myFirstMiddlewareTask;
}
@tbranyen
Copy link
Author

tbranyen commented Mar 13, 2017

Proposal: Create vTree Hook

function myMiddleware(options = {}) {
  function myMiddlewareTask() {}

  // Optional: Allows a middleware to hook into the tree creation process.
  //
  // If you wish to modify a VTree immediately after it has been created, you
  // need to specify the nodeType (Number) or nodeName (String) and a function
  // to run that accepts the Virtual Tree. 
  //
  // The value returned from this function becomes the new vTree. So if you
  // plan on only modifying the vTree, return the same instance, this will also
  // be more performant. These hooks are automatically unbound if the
  // middleware is unsubscribed.
  myMiddlewareTask.createVTreeHook(1, function(vTree) {
    /* Do something to the vTree */
    return vTree;
  });

  // Will only execute for divs.
  myMiddlewareTask.createVTreeHook('div', function(vTree) {
    /* Do something to the vTree */
    return vTree;
  });

  // Will only execute for fragments (also shadow dom and components).
  myMiddlewareTask.createVTreeHook(11, function(vTree) {
    /* Do something to the vTree */
    return vTree;
  });

  // Shows mirroring `data-some-property` to `key`.
  myMiddlewareTask.createVTreeHook(1, function(vTree) {
    if ('data-some-property' in vTree.attributes) {
      vTree.key = vTree.attributes['data-some-property'];
    }

    return vTree;
  });

  return myMiddlewareTask;
}

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