1
\$\begingroup\$

I have recently updated my JavaScript Module "Loader" to ES6 and would like some feedback on the code and how "ES6-y" it is. I previously asked about the ES5 version of my code here and I have implemented the feedback from @janos (thank you!) in the ES6 version.

(() => {
  const definitions = {},
        instances = {};

  const isDefined = (id) => definitions.hasOwnProperty(id);

  const define = (id, definition, asInstance) => {
    if(isDefined(id)) {
      throw new Error('module already defined: ' + id);
    }

    definitions[id] = definition;

    if (typeof(definition) !== 'function' || asInstance) {
      instances[id] = definition;
    }
  };

  const undefine = (id) => {
    if (!isDefined(id)) {
      throw new Error('module not defined: ' + id);
    }

    delete definitions[id];
    delete instances[id];
  };

  const require = (...ids) => {
    if (ids.length === 0) {
      return null;
    }

    const stack = [];

    const internalRequire = (...ids) => {
      var required = ids.map((id) => {
        if(!isDefined(id)) {
          throw new Error('module not defined: ' + id);
        }

        if (stack.indexOf(id) !== -1 && stack.push(id)) {
          throw new Error('cyclic dependency: ' + stack.join(' -> '));
        }

        stack.push(id);

        if (instances.hasOwnProperty(id)) {
          return instances[id];
        }

        return instances[id] = definitions[id].call(null, internalRequire);
      });

      return required.length === 1 ? required[0] : required;
    };

    return internalRequire(...ids);
  };

  window.module = Object.defineProperty({
    define: define,
    undefine: undefine,
    require: require
  }, 'modules', {
    get: Object.keys.bind(Object, definitions)
  });
})();

Amongst the changes made by @janos I also ended up changing the following:

  • Removed the create function and moved all of the code to the top-level IIFE.

  • Allowed require to accept multiple identifiers.

And as always, an example of how to use it:

module.define('person', () => {
  return class Person {
    constructor(name) {
      this.name = name;
    }

    speak(text) {
      return this.name + ': ' + text;
    }
  };
});

module.define('group', () => {
  return class Group {
    constructor(...members) {
      this.members = members;
    }

    speak(text) {
      return this.members.map((member) => member.speak(text));
    }
  };
});

module.define('main', (require) => {
  var [Person, Group] = require('person', 'group');

  var jack = new Person('Jack'),
      rocky = new Person('Rocky');

  var developers = new Group(jack, rocky);

  console.log(jack.speak('Hi Rocky!'));
  console.log(rocky.speak('Hi Jack!'));
  console.log(developers.speak('Hello everyone!'));
});

module.require('main');

One other semi-related question: which of these two is "best"?

module.define('without_return', () => class Example {
  doSomething() { }
});

module.define('with_return', () => {
  return class Example {
    doSomething() { }
  };
});

module.define('with_return_later', () => {
  class Example {
    doSomething() { }
  }

  return Example;
});

I'm not to sure on without_return as it's not that obvious that it is a class, however I don't know whether with_return or with_return_later is better, as they're both just as clear as one another.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.