lifeart/ember-ast-hot-load

View on GitHub
addon/helpers/hot-load.js

Summary

Maintainability
B
6 hrs
Test Coverage
import Helper from '@ember/component/helper';
import { inject as service } from '@ember/service';
import { later, cancel } from '@ember/runloop';

function hasPropertyNameInContext(prop, ctx) {
  if (typeof ctx !== 'object') {
    return false;
  }
  if (ctx === null) {
    return false;
  }
  return prop in ctx;
}

export default Helper.extend({
  hotLoader: service(),
  init() {
    this._super(...arguments);
    this.binded__rerenderOnTemplateUpdate = this.__rerenderOnTemplateUpdate.bind(
      this
    );
    const hotLoader = this.hotLoader;
    this.binded__willLiveReload = this.__willLiveReload.bind(this);
    hotLoader.registerWillHotReload(this.binded__rerenderOnTemplateUpdate);
    hotLoader.registerWillLiveReload(this.binded__willLiveReload);
  },
  __rerenderOnTemplateUpdate(path) {
    const hotLoader = this.hotLoader;
    if (hotLoader.isMatchingComponent(this.firstComputeName, path)) {
      hotLoader.forgetComponent(this.firstComputeName);
      cancel(this.timer);
      this.timer = later(() => {
        this.recompute();
      });
    }
  },
  __willLiveReload(event) {
    const hotLoader = this.hotLoader;
    if (
      hotLoader.isMatchingComponent(this.firstComputeName, event.modulePath)
    ) {
      event.cancel = true;
      (this.possibleNames || []).forEach((computedName) => {
        if (!event.components.includes(computedName)) {
          event.components.push(computedName);
        }
        hotLoader.clearRequirejs(computedName);
      });
    }
  },
  willDestroy() {
    this._super(...arguments);
    cancel(this.timer);
    const hotLoader = this.hotLoader;
    hotLoader.unregisterWillHotReload(this.binded__rerenderOnTemplateUpdate);
    hotLoader.unregisterWillLiveReload(this.binded__willLiveReload);
  },
  compute([
    name,
    context = {},
    maybePropertyValue = undefined,
    astStringName = '',
  ]) {
    const hotLoader = this.hotLoader;
    const safeAstName = String(astStringName || '');
    const renderComponentName = hotLoader.normalizeComponentName(name);
    const isComponent = hotLoader.isComponent(renderComponentName, context);
    this.possibleNames = [renderComponentName].concat(
      hotLoader.scopedComponentNames(renderComponentName, context)
    );

    // console.log('compute', {
    //   name, context, maybePropertyValue, astStringName,
    //   isComponent: hotLoader.isComponent(name, context),
    //   isHelper: hotLoader.isHelper(name)
    // });
    const hasPropInComponentContext = hasPropertyNameInContext(name, context);
    const isArgument =
      safeAstName.charAt(0) === '@' || safeAstName.startsWith('attrs.');
    if (
      !isArgument &&
      (hasPropInComponentContext || typeof maybePropertyValue !== 'undefined')
    ) {
      if (
        !hasPropInComponentContext &&
        !isComponent &&
        !hotLoader.isHelper(name)
      ) {
        // if it's not component, not in scope and not helper - dunno, we need to render placeholder with value;
        if (hotLoader.get('isFastBoot')) {
          return hotLoader.placeholderComponentName();
        }
        return hotLoader.renderDynamicComponentHelper(
          name,
          context,
          maybePropertyValue
        );
      }
    }
    if (!isComponent) {
      if (hotLoader.get('isFastBoot')) {
        return hotLoader.placeholderComponentName();
      }
      if (hotLoader.isHelper(name)) {
        hotLoader.registerDynamicComponent(name);
        return hotLoader.dynamicComponentNameForHelperWrapper(name);
      } else {
        return hotLoader.renderDynamicComponentHelper(
          name,
          context,
          maybePropertyValue
        );
      }
    }
    if (renderComponentName === this.firstCompute) {
      this.firstCompute = false;
      this.timer = later(() => {
        this.recompute();
      });
      return hotLoader.placeholderComponentName();
    }

    if (!this.firstCompute) {
      this.firstCompute = renderComponentName;
      this.firstComputeName = renderComponentName;
    }

    if (this.firstComputeName !== renderComponentName) {
      this.firstComputeName = renderComponentName;
    }

    return renderComponentName;
  },
});