Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Reduces lifecycle for environments with no dom #326

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 39 additions & 10 deletions packages/metal-component/src/Component.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
isElement,
isObject,
isServerSide,
isDom,
isString,
object,
} from 'metal';
Expand Down Expand Up @@ -93,6 +94,12 @@ class Component extends EventEmitter {
constructor(config, parentElement) {
super();

/**
* Wheter dom exists.
* @type {boolean}
*/
this.domExists = isDom();

/**
* Instance of `DomEventEmitterProxy` which proxies events from the component's
* element to the component itself.
Expand Down Expand Up @@ -159,8 +166,12 @@ class Component extends EventEmitter {
this.on('eventsChanged', this.onEventsChanged_);
this.addListenersFromObj_(this.dataManager_.get(this, 'events'));

this.created();
if (this.domExists) {
this.created();
}

this.componentCreated_ = true;

if (parentElement !== false) {
this.renderComponent(parentElement);
}
Expand Down Expand Up @@ -211,8 +222,10 @@ class Component extends EventEmitter {
parent: parentElement,
sibling: siblingElement,
};
this.emit('attached', this.attachData_);
this.attached();
if (this.domExists) {
this.emit('attached', this.attachData_);
this.attached();
}
}
return this;
}
Expand Down Expand Up @@ -273,15 +286,21 @@ class Component extends EventEmitter {
*/
detach() {
if (this.inDocument) {
this.emit('willDetach');
this.willDetach();
if (this.domExists) {
this.emit('willDetach');
this.willDetach();
}
if (this.element && this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
}
this.inDocument = false;
this.detached();
if (this.domExists) {
this.detached();
}
}
if (this.domExists) {
this.emit('detached');
}
this.emit('detached');
return this;
}

Expand All @@ -305,8 +324,10 @@ class Component extends EventEmitter {
*/
disposeInternal() {
this.detach();
this.disposed();
this.emit('disposed');
if (this.domExists) {
this.disposed();
this.emit('disposed');
}

this.elementEventProxy_.dispose();
this.elementEventProxy_ = null;
Expand Down Expand Up @@ -435,6 +456,9 @@ class Component extends EventEmitter {
* @protected
*/
handleStateWillChange_(event) {
if (!this.domExists) {
return;
}
this.willReceiveState(event.changes);
}

Expand All @@ -459,7 +483,9 @@ class Component extends EventEmitter {
this.forceUpdateCallback_();
this.forceUpdateCallback_ = null;
}

if (!this.domExists) {
return;
}
this.rendered(firstRender);
this.emit('rendered', firstRender);
}
Expand All @@ -470,6 +496,9 @@ class Component extends EventEmitter {
* @param {Object} changes
*/
informWillUpdate(...args) {
if (!this.domExists) {
return;
}
this.willUpdate(...args);
}

Expand Down
3 changes: 2 additions & 1 deletion packages/metal-incremental-dom/src/render/patch.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

import {append, exitDocument} from 'metal-dom';
import {isDom} from 'metal';
import {getData} from '../data';
import {render} from './render';

Expand All @@ -17,7 +18,7 @@ const patchingComponents_ = [];
function buildParentIfNecessary_(element) {
if (!element || !element.parentNode) {
let parent = {};
if (typeof document !== 'undefined') {
if (isDom()) {
parent = document.createElement('div');
}
if (element) {
Expand Down
66 changes: 63 additions & 3 deletions packages/metal-isomorphic/test/isomorphic.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ import MyComponent from './fixtures/MyComponent';
import MyJSXComponent from './fixtures/MyJSXComponent';
import ParentComponent from './fixtures/ParentComponent';
import {assert} from 'chai';
import {spy} from 'sinon';
import jsdomGlobal from 'jsdom-global';

const lifecycleList = ['created', 'rendered', 'willAttach', 'attached',
'willReceiveState', 'willReceiveProps', 'shouldUpdate',
'willUpdate', 'willDetach', 'detached', 'disposed'
];

describe('Isomorphic Rendering', () => {
it('should render soy component to string', () => {
assert.ok(!global.document);
Expand All @@ -14,7 +20,7 @@ describe('Isomorphic Rendering', () => {
});

assert.equal(htmlString, '<div>Hello, Soy!</div>');
});
});

it('should render jsx component to string', () => {
assert.ok(!global.document);
Expand All @@ -24,7 +30,34 @@ describe('Isomorphic Rendering', () => {
});

assert.equal(htmlString, '<div>Hello, JSX!</div>');
});
});

it('it should only perform willAttach lifecycle when not performing on DOM environment', () => {
assert.ok(!global.document);

const spies = lifecycleList.reduce((acc, cvalue) => {
acc[cvalue] = spy();
MyJSXComponent.prototype[cvalue] = acc[cvalue];
return acc;
}, {});

const htmlString = Component.renderToString(MyJSXComponent, {
message: 'Hello, JSX!',
});

assert.equal(htmlString, '<div>Hello, JSX!</div>');
assert.ok(spies['created'].notCalled);
assert.ok(spies['rendered'].notCalled);
assert.ok(spies['willAttach'].calledOnce);
assert.ok(spies['attached'].notCalled);
assert.ok(spies['willReceiveState'].notCalled);
assert.ok(spies['willReceiveProps'].notCalled);
assert.ok(spies['shouldUpdate'].notCalled);
assert.ok(spies['willUpdate'].notCalled);
assert.ok(spies['willDetach'].notCalled);
assert.ok(spies['detached'].notCalled);
assert.ok(spies['disposed'].notCalled);
});

it('should render soy component with subcomponents to string', () => {
assert.ok(!global.document);
Expand Down Expand Up @@ -65,7 +98,34 @@ describe('Isomorphic Rendering', () => {
});

assert.equal(comp.element.innerHTML, '<div>Hello, Soy!</div>');
});
});

it('it should perform all the lifecycles when performing on DOM environment', () => {
assert.ok(global.document);

const spies = lifecycleList.reduce((acc, cvalue) => {
acc[cvalue] = spy();
MyJSXComponent.prototype[cvalue] = acc[cvalue];
return acc;
}, {});

const htmlString = Component.renderToString(MyJSXComponent, {
message: 'Hello, JSX!',
});

assert.equal(htmlString, '<div>Hello, JSX!</div>');
assert.ok(spies['created'].calledOnce);
assert.ok(spies['rendered'].calledOnce);
assert.ok(spies['willAttach'].calledOnce);
assert.ok(spies['attached'].calledOnce);
assert.ok(spies['willReceiveState'].notCalled);
assert.ok(spies['willReceiveProps'].notCalled);
assert.ok(spies['shouldUpdate'].notCalled);
assert.ok(spies['willUpdate'].notCalled);
assert.ok(spies['willDetach'].calledOnce);
assert.ok(spies['detached'].calledOnce);
assert.ok(spies['disposed'].calledOnce);
});

it('should render jsx component to string', () => {
assert.ok(document);
Expand Down
3 changes: 3 additions & 0 deletions packages/metal-jsx/src/JSXComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class JSXComponent extends Component {
* @protected
*/
handleStateWillChange_(event) {
if (!this.domExists) {
return;
}
if (event.type !== 'state') {
this.willReceiveProps(event.changes);
}
Expand Down
8 changes: 8 additions & 0 deletions packages/metal/src/coreNamed.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,14 @@ export function isServerSide() {
);
}

/**
* Sets to true if running environment supports DOM.
* @return {boolean}
*/
export function isDom() {
return typeof window !== 'undefined' && typeof document !== 'undefined';
}

/**
* Null function used for default values of callbacks, etc.
* @return {void} Nothing.
Expand Down