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

Defactor: remove TypeScript, native classes / decorators, ember-concurrency #4

Merged
merged 5 commits into from
Jan 10, 2019
Merged
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
6 changes: 2 additions & 4 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ const fromPairs = require('lodash.frompairs');

module.exports = {
root: true,
parser: 'eslint-plugin-typescript/parser',
plugins: ['typescript', 'ember', 'prettier'],
plugins: ['ember', 'prettier'],
extends: [
'eslint:recommended',
'plugin:ember/recommended',
Expand All @@ -15,8 +14,7 @@ module.exports = {
rules: {
'ember/named-functions-in-promises': 'off',
'no-var': 'error',
'prefer-const': 'error',
'typescript/no-unused-vars': 'error'
'prefer-const': 'error'
},
overrides: [
// node files
Expand Down
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@
ember install ember-lazy-mount
```

If you're using a version below Ember 3.6, you also need to install the
[`ember-native-class-polyfill`](https://github.com/pzuraq/ember-native-class-polyfill).

## Usage

### `{{lazy-mount}}` Component
Expand Down
195 changes: 195 additions & 0 deletions addon/components/lazy-mount/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import Ember from 'ember';
import Component from '@ember/component';
import { get, set, setProperties } from '@ember/object';
import { assert } from '@ember/debug';
import { inject as service } from '@ember/service';
import { registerWaiter } from '@ember/test';
import template from './template';

/**
* The `{{lazy-mount}}` component works just like the
* [`{{mount}}` helper](https://emberjs.com/api/ember/3.5/classes/Ember.Templates.helpers/methods/mount?anchor=mount).
*
* It accepts the name of the engine as a positional parameter and also an
* optional `model` parameter.
*
* As soon as the helper is rendered, it will begin loading the specified
* engine. If the engine is already loaded, it will be mounted immediately.
*
* The `engineName` and `model` parameters are dynamic and you can update them.
* Setting a new `engineName` will cause the new engine to be loaded and mounted.
*
* #### Inline Usage
*
* While the engine is loading, nothing is rendered. If there was an error
* loading the engine, nothing is rendered.
*
* ```hbs
* {{lazy-mount engineName model=optionalDataForTheEngine}}
* ```
*
* #### Block Usage
*
* While the engine is loading or if there was an error loading the engine, the
* block that is passed to the component is rendered. The `engine` block
* parameter is an object with two properties:
*
* - **`isLoading`**: _`boolean`_ — Whether or not the engine is currently
* loading
* - **`error`**: _`Error | null`_ — If there was an error loading the engine
*
* When the engine was loaded successfully, the passed in block is replaced by
* the engine.
*
* ```hbs
* {{#lazy-mount engineName model=optionalDataForTheEngine as |engine|}}
* {{#if engine.isLoading}}
* 🕑 The engine is loading...
* {{else if engine.error}}
* 😨 There was an error loading the engine:
* <code>{{engine.error}}</code>
* {{/if}}
* {{/lazy-mount}}
* ```
*
* @class LazyMountComponent
* @param {string} name Name of the engine to mount.
* @param {any} [model] Object that will be set as
* the model of the engine.
* @public
*/
export default Component.extend({
buschtoens marked this conversation as resolved.
Show resolved Hide resolved
tagName: '',
layout: template,

engineLoader: service(),

/**
* The name of the engine to load and subsequently mount.
*
* @property name
* @type {string}
* @public
*/
name: null,

/**
* Optional model that will be passed through to the engine.
*
* @see https://emberjs.com/api/ember/3.7/classes/Ember.Templates.helpers/methods/mount?anchor=mount
*
* @property model
* @type {any?}
* @public
*/
model: null,

/**
* When the engine was loaded successfully, this will then be the name of the
* engine. Presence of this field therefore indicates that the engine was
* loaded successfully.
*
* This field is also used by `didReceiveAttrs` for diffing.
*
* @property loadedName
* @type {string?}
* @private
*/
loadedName: null,

/**
* If an error occurred while loading the engine, it will be set here.
*
* @property error
* @type {Error?}
* @private
*/
error: null,

/**
* While the bundle is being loaded, this property is `true`.
*
* @property isLoading
* @type {boolean}
* @private
*/
isLoading: false,

didReceiveAttrs() {
this._super();

const name = get(this, 'name');
assert(`lazy-mount: Argument 'name' is missing.`, name);

if (name !== get(this, 'loadedName')) {
// only load a new engine, if it is different from the last one
this.loadEngine(name);
}
},

/**
* Manages the life cycle of loading an engine bundle and setting the
* following properties in accordance:
*
* - `isLoading`
* - `error`
* - `loadedName`
*
* Called by `didReceiveAttrs`.
*
* @method loadEngine
* @param {string} name
* @async
* @private
*/
async loadEngine(name = get(this, 'name')) {
const shouldCancel = this._thread();
const engineLoader = get(this, 'engineLoader');

this.setLoading();

if (!engineLoader.isLoaded(name)) {
try {
await engineLoader.load(name);
if (shouldCancel()) return;
} catch (error) {
if (shouldCancel()) return;
this.setError(error);
return;
}
}

this.setLoaded(name);
},

setLoading() {
setProperties(this, { loadedName: null, error: null, isLoading: true });
},
setLoaded(loadedName) {
setProperties(this, { loadedName, error: null, isLoading: false });
},
setError(error) {
setProperties(this, { loadedName: null, error, isLoading: false });
},

/**
* The following is a really low-fidelity implementation of something that
* would be handled by ember-concurrency or ember-lifeline.
*/

_threadId: null,

_thread() {
if (Ember.testing) {
registerWaiter(this, () => !get(this, 'isLoading'));
}

const threadId = set(this, '_threadId', {});
return () =>
get(this, 'isDestroyed') ||
get(this, 'isDestroying') ||
get(this, '_threadId') !== threadId;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@buschtoens I don't understand this line, because it's always will be equal

{} !=== {}

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked about this on Discord, but for anyone following along at home and also wondering:

Previously loadEngine was a restartable ember-concurrency task, meaning that whenever a new instance is started, any old potentially still running instance is cancelled. This is to make sure that, we don't accidentally render stale content, because of async calls, when the input arguments change.

The trick here with {} is:

{} === {} // => false

const obj = {};
obj === obj // => true

Whenever loadEngine is called it starts a new "thread", which then sets this._threadId to a new object. So any old still running threads would then compare there own closure threadId to the new this._threadId, which is then unequal.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sound good, thank you bud

}
}).reopenClass({
positionalParams: ['name']
});
55 changes: 0 additions & 55 deletions addon/components/lazy-mount/component.ts

This file was deleted.

4 changes: 0 additions & 4 deletions addon/components/lazy-mount/template.d.ts

This file was deleted.

7 changes: 0 additions & 7 deletions addon/interfaces/task.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import Service from '@ember/service';
import { get } from '@ember/object';
import { getOwner } from '@ember/application';
import { service } from '@ember-decorators/service';
import { inject as service } from '@ember/service';
import require from 'require';
import AssetLoaderService from 'ember-asset-loader/services/asset-loader';

export default class EngineLoaderService extends Service {
@service assetLoader!: AssetLoaderService;
export default Service.extend({
assetLoader: service(),

/**
* Checks the owner to see if it has a registration for an Engine. This is a
Expand All @@ -17,10 +16,10 @@ export default class EngineLoaderService extends Service {
* @param {String} name
* @return {Boolean}
*/
isLoaded(name: string) {
isLoaded(name) {
const owner = getOwner(this);
return owner.hasRegistration(`engine:${name}`);
}
},

/**
* Registers an Engine that was recently loaded.
Expand All @@ -29,24 +28,24 @@ export default class EngineLoaderService extends Service {
*
* @param {String} name
*/
register(name: string) {
register(name) {
if (this.isLoaded(name)) return;

const owner = getOwner(this);
owner.register(`engine:${name}`, require(`${name}/engine`).default);
}
},

/**
* Loads and registers a lazy Engine.
*
* @param {String} name
* @async
*/
async load(name: string) {
async load(name) {
if (this.isLoaded(name)) return;

const assetLoader = get(this, 'assetLoader');
await assetLoader.loadBundle(name);
this.register(name);
}
}
});
Loading