diff --git a/package.json b/package.json index efac420ef..26e1c02b0 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "es6-promise": "^3.1.2", "es6-shim": "^0.35.0", "file-loader": "^0.8.5", - "md5": "^2.2.1", "msgpack-lite": "^0.1.20", "object-assign": "4.0.1", "postcss-cssnext": "^2.5.2", diff --git a/src/frontend/components/render-state/render-state.ts b/src/frontend/components/render-state/render-state.ts index a3e8e880e..171d2de07 100644 --- a/src/frontend/components/render-state/render-state.ts +++ b/src/frontend/components/render-state/render-state.ts @@ -1,5 +1,3 @@ -import md5 = require('md5'); - import { ChangeDetectionStrategy, Component, @@ -125,7 +123,7 @@ export class RenderState { } private getMetadata(key: string): [ObjectType, any] { - return this.metadata.get(md5(serializePath(this.path.concat([key])))); + return this.metadata.get(this.state[key]); } private isEmittable(key: string): boolean { diff --git a/src/frontend/frontend.ts b/src/frontend/frontend.ts index 2fdd7dc3b..21141ddfe 100644 --- a/src/frontend/frontend.ts +++ b/src/frontend/frontend.ts @@ -263,7 +263,10 @@ class App { if (typeof beforeLoad === 'function') { beforeLoad(); } - return response; + + const {instance, metadata} = response; + + return {instance, metadata: new Map(metadata)}; }); this.componentState.wait(node, promise); diff --git a/src/tree/metadata.ts b/src/tree/metadata.ts index 4723ae63c..3408dc291 100644 --- a/src/tree/metadata.ts +++ b/src/tree/metadata.ts @@ -1,5 +1,3 @@ -import md5 = require('md5'); - import { AsyncSubject, BehaviorSubject, @@ -9,12 +7,6 @@ import { Subject, } from 'rxjs'; -import { - Path, - serializePath, - deserializePath, -} from './path'; - import {Node} from './node'; import { @@ -42,53 +34,54 @@ export enum ObjectType { ContentChildren = 0x100, } -export type Metadata = Map; +export type Metadata = Map; export interface InstanceWithMetadata { instance: any; metadata: Metadata; } -export const instanceWithMetadata = (node: Node, instance): InstanceWithMetadata => { +// It is imperative that the metadata and the instance value itself travel together +// through the serializer, otherwise we are going to have to serialize the entire +// object structure twice, once for the instance and once for the metadata. But if +// we put them together as part of the same object, the serializer will be smart +// enough not to duplicate objects. If someone breaks apart the instance and the +// metadata into two objects, a lot of code that depends on reference equality is +// going to get broken! So do not change this! +export const instanceWithMetadata = (node: Node, instance) => { if (node == null || instance == null) { return null; } const metadata = new Map(); - const nodePath = deserializePath(node.id); - - const serialize = (path: Path): string => md5(serializePath(path)); - - recurse(nodePath, instance, - (path: Path, obj) => { + recurse(instance, + obj => { let type = objectType(obj); - const update = (p: Path, flag: ObjectType, additionalProps) => { - const serializedPath = serialize(p); - - const existing = metadata.get(serializedPath); + const update = (flag: ObjectType, additionalProps) => { + const existing = metadata.get(obj); if (existing) { existing[0] |= flag; Object.assign(existing, additionalProps); } else { - metadata.set(serializedPath, [flag, additionalProps]); + metadata.set(obj, [flag, additionalProps]); } }; const component = componentMetadata(obj); if (component) { for (const input of componentInputs(component, obj)) { - update(path.concat([input.propertyKey]), ObjectType.Input, {alias: input.bindingPropertyName}); + update(ObjectType.Input, {alias: input.bindingPropertyName}); } for (const output of componentOutputs(component, obj)) { - update(path.concat([output.propertyKey]), ObjectType.Output, {alias: output.bindingPropertyName}); + update(ObjectType.Output, {alias: output.bindingPropertyName}); } const addQuery = (decoratorType: string, objectType: ObjectType) => { for (const vc of componentQueryChildren(decoratorType, component, obj)) { - update(path.concat([vc.propertyKey]), objectType, {selector: vc.selector}); + update(objectType, {selector: vc.selector}); } }; @@ -99,19 +92,17 @@ export const instanceWithMetadata = (node: Node, instance): InstanceWithMetadata } if (type !== 0) { - const serializedPath = serialize(path); - - const existing = metadata.get(serializedPath); + const existing = metadata.get(obj); if (existing) { - metadata.set(serializedPath, [existing[0] | type, existing[1]]); + metadata.set(obj, [existing[0] | type, existing[1]]); } else { - metadata.set(serializedPath, [type, null]); + metadata.set(obj, [type, null]); } } }); - return {instance, metadata}; + return {instance, metadata: Array.from( metadata)}; }; const objectType = (object): ObjectType => { diff --git a/src/utils/circular-recurse.ts b/src/utils/circular-recurse.ts index 91a74f8f0..c1caf4339 100644 --- a/src/utils/circular-recurse.ts +++ b/src/utils/circular-recurse.ts @@ -1,29 +1,36 @@ import {Path} from '../tree'; -export type Apply = (path: Path, value) => void; +import {isScalar} from './scalar'; -export const recurse = (initialPath: Path, object, exec: Apply) => { +export type Apply = (value) => void; + +// Recursive traversal of an object tree, but will not traverse circular references or DOM elements +export const recurse = (object, apply: Apply) => { const visited = new Set(); - const apply = (path: Path, value, fn: Apply) => { - if (value == null || visited.has(value)) { + const visit = value => { + if (value == null || isScalar(value) || /Element/.test(Object.prototype.toString.call(value))) { + return; + } + + if (visited.has(value)) { // circular loop return; } visited.add(value); - fn(path, value); + apply(value); if (Array.isArray(value) || value instanceof Set) { - (value).forEach((v, k) => apply(path.concat([k]), v, fn)); + (value).forEach((v, k) => visit(v)); } else if (value instanceof Map) { - value.forEach((v, k) => apply(path.concat([k]), v, fn)); + value.forEach((v, k) => visit(v)); } else { - Object.keys(value).forEach(k => apply(path.concat([k]), value[k], fn)); + Object.keys(value).forEach(k => visit(value[k])); } }; - apply(initialPath, object, exec); + visit(object); }; diff --git a/src/utils/scalar.ts b/src/utils/scalar.ts index 22ba67e7e..9df1438b8 100644 --- a/src/utils/scalar.ts +++ b/src/utils/scalar.ts @@ -1,6 +1,7 @@ export const isScalar = value => { switch (typeof value) { case 'string': + case 'number': case 'boolean': case 'function': case 'undefined': diff --git a/typings.json b/typings.json index f036b8bf1..9b4cfe587 100644 --- a/typings.json +++ b/typings.json @@ -17,7 +17,6 @@ "es6-shim": "registry:dt/es6-shim#0.31.2+20160602141504", "filesystem": "registry:dt/filesystem#0.0.0+20160316155526", "filewriter": "registry:dt/filewriter#0.0.0+20160316155526", - "md5": "registry:dt/md5#2.1.0+20160521153251", "node": "registry:dt/node#6.0.0+20160823142437", "tape": "registry:dt/tape#4.2.2+20160317120654", "webrtc/mediastream": "registry:dt/webrtc/mediastream#0.0.0+20160317120654"