Skip to content

Commit

Permalink
Greatly improve types and empty values
Browse files Browse the repository at this point in the history
  • Loading branch information
tbranyen committed Sep 7, 2020
1 parent de8d151 commit 919a80d
Show file tree
Hide file tree
Showing 29 changed files with 353 additions and 204 deletions.
4 changes: 4 additions & 0 deletions packages/babel-preset-diffhtml-imports/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { join } = require('path');

const ModuleRewrite = require('babel-plugin-module-rewrite').default;
const OptionalChaining = require('@babel/plugin-proposal-optional-chaining').default;
const ObjectRestSpread = require('@babel/plugin-proposal-object-rest-spread').default;
const ClassProperties = require('@babel/plugin-proposal-class-properties').default;
const ModulesCommonJS = require('@babel/plugin-transform-modules-commonjs').default;
Expand Down Expand Up @@ -37,6 +38,7 @@ if (NODE_ENV === 'umd' || NODE_ENV === 'min') {
TemplateLiterals,
Classes,
ForOf,
OptionalChaining,
];
}

Expand All @@ -48,6 +50,7 @@ if (NODE_ENV === 'cjs') {
ObjectRestSpread,
ClassProperties,
ElementClasses,
OptionalChaining,
];
}

Expand All @@ -66,6 +69,7 @@ if (NODE_ENV === 'test' || NODE_ENV === 'test+cov') {
ModulesCommonJS,
ObjectRestSpread,
ClassProperties,
OptionalChaining,
];
};

Expand Down
1 change: 1 addition & 0 deletions packages/babel-preset-diffhtml-imports/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
"@babel/plugin-proposal-optional-chaining": "^7.11.0",
"@babel/plugin-transform-arrow-functions": "^7.2.0",
"@babel/plugin-transform-block-scoping": "^7.6.0",
"@babel/plugin-transform-classes": "^7.5.5",
Expand Down
6 changes: 3 additions & 3 deletions packages/diffhtml/lib/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import createTree from './tree/create';
import parse, { TOKEN } from './util/parse';
import escape from './util/escape';
import decodeEntities from './util/decode-entities';
import { VTree, Supplemental } from './util/types';
import { EMPTY, VTree, Supplemental } from './util/types';

const { isArray } = Array;
const isTagEx = /(<|\/)/;
Expand All @@ -25,7 +25,7 @@ const nextValue = (/** @type {any[]} */ values) => {
* @return {VTree} VTree object or null if no input strings
*/
export default function handleTaggedTemplate(strings, ...values) {
const empty = createTree('#text', '');
const empty = createTree('#text', EMPTY.STR);

// Do not attempt to parse empty strings.
if (!strings) {
Expand Down Expand Up @@ -55,7 +55,7 @@ export default function handleTaggedTemplate(strings, ...values) {
}

// Used to store markup and tokens.
let HTML = '';
let HTML = EMPTY.STR;

// We filter the supplemental values by where they are used. Values are
// either, children, or tags (for components).
Expand Down
4 changes: 2 additions & 2 deletions packages/diffhtml/lib/inner-html.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Transaction, { defaultTasks } from './transaction';
import { ValidInput, Options, Mount } from './util/types';
import { EMPTY, ValidInput, Options, Mount } from './util/types';

/**
*
Expand All @@ -9,7 +9,7 @@ import { ValidInput, Options, Mount } from './util/types';
*
* @return {Promise<Transaction> | unknown}
*/
export default function innerHTML(domNode, input = '', options = {}) {
export default function innerHTML(domNode, input = EMPTY.STR, options = EMPTY.OBJ) {
options.inner = true;
options.executeScripts = 'executeScripts' in options ? options.executeScripts : true;
options.tasks = options.tasks || defaultTasks;
Expand Down
16 changes: 8 additions & 8 deletions packages/diffhtml/lib/node/create.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NodeCache, CreateNodeHookCache } from '../util/caches';
import process from '../util/process';
import globalThis from '../util/global';
import { VTreeLike, VTree, ValidNode } from '../util/types';
import { VTreeLike, VTree, ValidNode, EMPTY } from '../util/types';
import createTree from '../tree/create';

const namespace = 'http://www.w3.org/2000/svg';
Expand All @@ -15,7 +15,7 @@ const document = /** @type {any} */ (globalThis).document || null;
* @param {VTreeLike} vTreeLike - A Virtual Tree Element or VTree-like element
* @param {Document=} ownerDocument - Document to create Nodes in, defaults to document
* @param {Boolean=} isSVG - Indicates if the root element is SVG
* @return {ValidNode} A DOM Node matching the vTree
* @return {ValidNode | null} A DOM Node matching the vTree
*/
export default function createNode(vTreeLike, ownerDocument = document, isSVG) {
if (process.env.NODE_ENV !== 'production') {
Expand All @@ -42,13 +42,13 @@ export default function createNode(vTreeLike, ownerDocument = document, isSVG) {

isSVG = isSVG || nodeName === 'svg';

/**
* Will vary based on the properties of the VTree.
* @type {ValidNode | unknown}
*/
/** @type {ValidNode | null} */
let domNode = null;

CreateNodeHookCache.forEach((fn, retVal) => {
/** @type {ValidNode | null | void} */
let retVal = null;

CreateNodeHookCache.forEach((fn) => {
// Invoke all the `createNodeHook` functions passing along the vTree as the
// only argument. These functions must return a valid DOM Node value.
if (retVal = fn(vTree)) {
Expand All @@ -68,7 +68,7 @@ export default function createNode(vTreeLike, ownerDocument = document, isSVG) {
// Create empty text elements. They will get filled in during the patch
// process.
if (nodeName === '#text') {
domNode = ownerDocument.createTextNode(vTree.nodeValue || '');
domNode = ownerDocument.createTextNode(vTree.nodeValue || EMPTY.STR);
}
// Support dynamically creating document fragments.
else if (nodeName === '#document-fragment') {
Expand Down
12 changes: 6 additions & 6 deletions packages/diffhtml/lib/node/patch.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import createNode from './create';
import { runTransitions } from '../transition';
import { protectVTree, unprotectVTree } from '../util/memory';
import decodeEntities from '../util/decode-entities';
import { PATCH_TYPE, ValidNode, VTree } from '../util/types';
import { PATCH_TYPE, ValidNode, VTree, EMPTY } from '../util/types';
import { NodeCache, TransitionCache } from '../util/caches';

const { keys } = Object;
Expand Down Expand Up @@ -64,7 +64,7 @@ const setAttribute = (vTree, domNode, name, value) => {

// If we cannot set the value as a property, try as an attribute.
if (lowerName) {
htmlElement.setAttribute(lowerName, noValue ? '' : value);
htmlElement.setAttribute(lowerName, noValue ? EMPTY.STR : value);
}
}
// Support patching an object representation of the style object.
Expand Down Expand Up @@ -146,7 +146,7 @@ const changeNodeValue = (domNode, nodeValue) => {
* @param {*} patches
* @param {*} state
*/
export default function patchNode(patches, state = {}) {
export default function patchNode(patches, state = EMPTY.OBJ) {
const promises = [];
const { ownerDocument, svgElements = new Set() } = state;
const { length } = patches;
Expand Down Expand Up @@ -333,9 +333,9 @@ export default function patchNode(patches, state = {}) {

// Check if there are any transitions, if none, then we can skip
// inserting and removing, and instead use a much faster replaceChild.
const hasAttached = TransitionCache.get('attached').size;
const hasDetached = TransitionCache.get('detached').size;
const hasReplaced = TransitionCache.get('replaced').size;
const hasAttached = TransitionCache.get('attached')?.size;
const hasDetached = TransitionCache.get('detached')?.size;
const hasReplaced = TransitionCache.get('replaced')?.size;

// In the hot path, ensure we always use the fastest operation given
// the constraints. If there are no transitions, do not use the more
Expand Down
4 changes: 2 additions & 2 deletions packages/diffhtml/lib/outer-html.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Transaction, { defaultTasks } from './transaction';
import { ValidInput, Options, Mount } from './util/types';
import { EMPTY, ValidInput, Options, Mount } from './util/types';

/**
*
Expand All @@ -9,7 +9,7 @@ import { ValidInput, Options, Mount } from './util/types';
*
* @return {Promise<Transaction> | unknown}
*/
export default function outerHTML(domNode, input = '', options = {}) {
export default function outerHTML(domNode, input = EMPTY.STR, options = EMPTY.OBJ) {
options.inner = false;
options.tasks = options.tasks || defaultTasks;
options.executeScripts = 'executeScripts' in options ? options.executeScripts : true;
Expand Down
4 changes: 2 additions & 2 deletions packages/diffhtml/lib/release.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { StateCache, NodeCache, ReleaseHookCache } from './util/caches';
import { unprotectVTree } from './util/memory';
import { ValidNode } from './util/types';
import { Mount } from './util/types';

/**
* Releases state and memory associated to a DOM Node.
*
* @param {ValidNode} domNode - Valid input node
* @param {Mount} domNode - Valid input node
*/
export default function release(domNode) {
// Try and find a state object for this DOM Node.
Expand Down
5 changes: 4 additions & 1 deletion packages/diffhtml/lib/tasks/parse-new-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import parse from '../util/parse';
import createTree from '../tree/create';
import Transaction from '../transaction';

export default function parseNewTree(/** @type {Transaction} */ transaction) {
/**
* @param {Transaction} transaction
*/
export default function parseNewTree(transaction) {
const { state, markup, options } = transaction;
const { measure } = state;
const { inner } = options;
Expand Down
3 changes: 1 addition & 2 deletions packages/diffhtml/lib/tasks/patch-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import globalThis from '../util/global';
*/
export default function patch(transaction) {
const { domNode, state, state: { measure, scriptsToExecute }, patches } = transaction;
/** @type {HTMLElement | DocumentFragment} */
const { ownerDocument } = (domNode);
const { ownerDocument } = /** @type {HTMLElement} */ (domNode);
const promises = transaction.promises || [];

state.ownerDocument = ownerDocument || globalThis.document;
Expand Down
5 changes: 3 additions & 2 deletions packages/diffhtml/lib/tasks/reconcile-trees.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ export default function reconcileTrees(transaction) {
const { state, domNode, markup, options } = transaction;
const { previousMarkup } = state;
const { inner } = options;
const { outerHTML } = /** @type {any} */ (domNode);
const domNodeAsHTMLEl = /** @type {HTMLElement} */ (domNode);
const { outerHTML } = domNodeAsHTMLEl;

// We rebuild the tree whenever the DOM Node changes, including the first
// time we patch a DOM Node.
if (previousMarkup !== outerHTML || !state.oldTree || !outerHTML) {
release(domNode);
state.oldTree = createTree(domNode);
state.oldTree = createTree(domNodeAsHTMLEl);
protectVTree(state.oldTree);

// Reset the state cache after releasing.
Expand Down
6 changes: 3 additions & 3 deletions packages/diffhtml/lib/tasks/sync-trees.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import syncTree from '../tree/sync';
import createNode from '../node/create';
import { StateCache } from '../util/caches';
import { PATCH_TYPE } from '../util/types';
import { PATCH_TYPE, EMPTY, Mount } from '../util/types';
import process from '../util/process';
import Transaction from '../transaction';

Expand Down Expand Up @@ -51,12 +51,12 @@ export default function syncTrees(/** @type {Transaction} */ transaction) {

// Update the StateCache since we are changing the top level element.
StateCache.delete(domNode);
StateCache.set(newNode, state);
StateCache.set(/** @type {Mount} */ (newNode), state);

transaction.domNode = /** @type {HTMLElement} */ (newNode);

if (newTree.nodeName === 'script') {
state.scriptsToExecute.set(newTree, newTree.attributes.type || '');
state.scriptsToExecute.set(newTree, newTree.attributes.type || EMPTY.STR);
}
}
// Synchronize the top level elements.
Expand Down
35 changes: 18 additions & 17 deletions packages/diffhtml/lib/transaction.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { VTree, ValidInput, Mount, Options } from './util/types';
import { StateCache, MiddlewareCache, NodeCache } from './util/caches';
import { VTree, ValidInput, Mount, Options, TransactionState, EMPTY } from './util/types';
import { MiddlewareCache, StateCache, NodeCache } from './util/caches';
import { gc } from './util/memory';
import makeMeasure from './util/make-measure';
import process from './util/process';
Expand Down Expand Up @@ -109,11 +109,13 @@ export default class Transaction {
* @param {Options} options
*/
constructor(domNode, input, options) {
// TODO: Rename this to mount.
this.domNode = domNode;
// TODO: Rename this to input.
this.markup = input;
this.options = options;

/** @type {TransactionState} */
this.state = StateCache.get(domNode) || {
measure: makeMeasure(domNode, input),
svgElements: new Set(),
Expand Down Expand Up @@ -191,31 +193,30 @@ export default class Transaction {
// Rendering is complete.
state.isRendering = false;

/**
* Ensure correct script type is set before caching the output HTML.
* @type {Map<VTree, string | undefined>}
*/(scriptsToExecute).forEach((type = '', key) => {
const oldNode = /** @type {any} */ (NodeCache.get(key));
scriptsToExecute.forEach((type = EMPTY.STR, vTree) => {
const oldNode = /** @type {HTMLElement} */ (NodeCache.get(vTree));

// Reset the type value.
if (type) oldNode.setAttribute('type', type);
else oldNode.removeAttribute('type');
});

// Save the markup immediately after patching.
state.previousMarkup = 'outerHTML' in /** @type {any} */ (domNode) ? domNode.outerHTML : '';
state.previousMarkup = 'outerHTML' in /** @type {any} */ (domNode) ? domNode.outerHTML : EMPTY.STR;

// Only execute scripts if the configuration is set. By default this is set
// to true. You can toggle this behavior for your app to disable script
// execution.
if (options.executeScripts) {
/**
* Execute deferred scripts.
* @type {Map<VTree, string | undefined>}
*/(scriptsToExecute).forEach((_, key)=> {
const oldNode = NodeCache.get(key);
// Execute deferred scripts.
scriptsToExecute.forEach((_, vTree)=> {
const oldNode = NodeCache.get(vTree);
const newNode = /** @type {any} */ (oldNode).cloneNode(true);

if (!oldNode) {
return;
}

// If the script is now the root element, make sure we cleanup and
// re-assign.
if (StateCache.has(oldNode)) {
Expand All @@ -224,10 +225,10 @@ export default class Transaction {
}

// Replace the node association.
NodeCache.set(key, newNode);
NodeCache.set(vTree, newNode);

// Replace the scripts to trigger default browser behavior.
/** @type {any} */ (oldNode).parentNode.replaceChild(newNode, oldNode);
oldNode.parentNode && oldNode.parentNode.replaceChild(newNode, oldNode);
});
}

Expand Down Expand Up @@ -259,10 +260,10 @@ export default class Transaction {
}

/** @type {Mount} */
domNode = '';
domNode = EMPTY.STR;

/** @type {ValidInput} */
markup = '';
markup = EMPTY.STR;

/** @type {VTree=} */
oldTree = undefined;
Expand Down
Loading

0 comments on commit 919a80d

Please sign in to comment.