Currently, I have 4 Child Classes each in their own file. I'm requiring them all in the same file. I am wondering if I can contain all 4 of those classes in a single module. Currently, I'm importing them like this
var Jack = require('./Jack.js');
var JackInstance = new Jack();
var Jones = require('./Jones.js');
var JonesInstance = new Jones();
I'd like to import them like this
var People = require('./People.js');
var JackInstance = new People.Jack();
Or even
var Jack = require('./People.js').Jack;
var JackInstance = new Jack();
My classes are defined like so
class Jack{
//Memeber variables, functions, etc
}
module.exports = Jack;
You can export multiple classes like this:
e.g. People.js
class Jack{
//Member variables, functions, etc
}
class John{
//Member variables, functions, etc
}
module.exports = {
Jack : Jack,
John : John
}
And access these classes as you have correctly mentioned:
var People = require('./People.js');
var JackInstance = new People.Jack();
var JohnInstance = new People.John();
You can also do this in a shorter form, using destructuring assignments (which are supported natively starting from Node.js v6.0.0):
// people.js
class Jack {
// ...
}
class John {
// ...
}
module.exports = { Jack, John }
Importing:
// index.js
const { Jack, John } = require('./people.js');
Or even like this if you want aliased require assignments:
// index.js
const {
Jack: personJack, John: personJohn,
} = require('./people.js');
In the latter case personJack and personJohn will reference your classes.
A word of warning:
Destructuring could be dangerous in sense that it's prone to producing unexpected errors. It's relatively easy to forget curly brackets on export or to accidentally include them on require.
Node.js 12 update:
Lately ECMAScript Modules received an extended support in Node.js 12.*, introducing the convenient usage of import statement to accomplish the same task (currently Node should be started with a flag --experimental-modules in order to make them available).
// people.mjs
export class Jack {
// ...
}
export class John {
// ...
}
Notice that files that adhere to modules convention should have an .mjs extension.
// index.mjs
import {
Jack as personJack, John as personJohn,
} from './people.mjs';
This is much better in a sense of robustness and stability, as an attempt to import non-existing export from the module will throw an exception similar to:
SyntaxError: The requested module 'x' does not provide an export named
'y'
Related
Eventually what I need is to build a class declaration at runtime providing dynamic parameters to a property annotation:
import {Type} from "external-module";
export default class TypeWrapper {
#Type(() => '{this part of the class declaration should be changable at runtime}')
data
}
I have a feeling that this should be possible to achieve but can't figure out a proper way yet.
As a proof of concept I was trying to do something like below:
let MyClass = eval('(class MyClass{})')
let myClass = new MyClass()
That works, however MyClass needs to define some imports:
let MyClass = eval('import {Type} from "external-module"' +
'(class MyClass{})')
That one fails with "Cannot use import statement outside a module" which is quite expected.
Another approach I tried is to load a module from string:
var moduleData = '' +
'import module from "./module/path/file.js"\n' +
'\n' +
'export default class MyClass {\n' +
'}\n' +
'\n';
var b64moduleData = "data:text/javascript;base64," + btoa(moduleData);
let MyClass = await import(b64moduleData)
But it fails with "Cannot find module", suggesting it assumes b64moduleData is a path rather than module data itself.
Anyone has any suggestions?
Normally, for this type of thing I would use a class factory:
import { Type } from 'some-module';
function createClass(parameters) {
return class {
....
}
}
const MyClass = createClass(...);
I'm unsure if this fits your particular use case based on the details provided.
EDIT: As a side-note, as far as I know, you cannot construct modules from a string, which is what your code is doing, and what the compiler is complaining about.
I looked at similar questions/answers but they are different from my question or my setup.
I've a class B that extends class A and writing a test for the class B.
src folder
src/a/A.js
export default class A {
// implementation
}
src/b/B.js
import A from "../a/A" // -> SyntaxError: Cannot use import statement outside a module
export default class B extends A {
constructor() {
}
}
test folder
describe("BTest", function(){
let assert = require("assert");
describe("testConstructor", function(){
let B = require("../src/b/B");
let b = new B();
assert(b != null)
});
});
You appear to be running in a node environment. Node doesn't support import/export. You’ll need to either use require and module.exports or transpile your code using Babel.
It seems like you should be able to do this because building a form dynamically based off of a class definition (Angular) would work so much better if the logic could be written agnostically of the class. This would be scalable, so an addition of a field to the class would not also require an update to the logic producing the form and template.
Is there any way to do this or even an NPM module that will do this?
I found that I can do ClassName.toString() but it would be a pain to parse that. And I just might write a module to do it if nothing else.
I just feel like instantiating a dummy instance of the class for the purpose of enumerating over its properties is a poor strategy.
You could use Object.getOwnPropertyNames().
Example class:
class Foo {
setBar() {
throw Error('not implemented');
return false;
}
getBar() {
throw Error('not implemented');
return false;
}
}
And then
Object.getOwnPropertyNames(Foo.prototype)
results in
["constructor", "setBar", "getBar"]
While I was researching this I looked into Object.keys first, and although it didn't work, you may wish to reference the documentation for Object.keys's polyfill. It has code for stripping out constructor, toString, and the like, as well as properly implementing hasOwnProperty.
Also see Bergi's answer here.
Any way? Declare your class as a function, and put the properties on the prototype:
var Presidents = function() {
};
Presidents.prototype = {
"washington" : "george",
"adams" : "john"
}
console.log(Object.keys(Presidents.prototype))
// Output is
// [ 'washington', 'adams' ]
getOwnPropertyDescriptors of a class prototype will only expose methods and accessor descriptors - data properties can not be determined without instantiation (also because constructor arguments can influence the amount, types and values of props). There can be several reasons to not want to instantiate (e.g. because some static counter tracks instances) - therefore a workaround could be to dynamically create a copy of the class and instatiate that "shadow" along with sample constructor arguments.
/**
* get properties from class definition without instantiating it
*
* #param cls: class
* #param args: sample arguments to pass to shadow constructor
* #usage `const props = getInstanceProperties(MyClass);`
* #notice this will regex replace the classname (can be an issue with strings containing that substring)
*/
const getInstanceProperties = (cls, args = [], ignore = ['constructor', 'toString']) => {
const className = cls.prototype.constructor.name;
const shadowCode = cls.toString().replace(new RegExp(`${className}`, 'g'), `_${className}_`);
const shadowClass = eval(`(${shadowCode})`);
const o = new shadowClass(...args);
const methodsAndAccessors = Object.getOwnPropertyDescriptors(cls.prototype);
const dataDescriptors = Object.getOwnPropertyDescriptors(o);
const descriptors = Object.assign({}, methodsAndAccessors, dataDescriptors);
ignore.forEach(name => delete descriptors[name]);
return descriptors;
};
class Foo extends Object {
static instances = 0;
#myPrivateVar = 123;
myValue=123;
constructor(){
super();
this.myConstructorVar = ++Foo.instances;
}
myMethod() {}
set myAccessor(x){}
}
console.log(Object.keys(getInstanceProperties(Foo)));
will return:
[ 'myMethod', 'myAccessor', 'myValue', 'myConstructorProp' ]
I'm in a weird situation that i need to instantiate a new Class with a string stored in a variable but even i'm sure the class name is correct i get an error that given class name is not a constructor
Here is a dummy code that doesn't work:
class Foo {
constructor(){
console.log('Foo!');
}
};
const foo = 'Foo';
const bar = new window[foo]();
console.log(bar);
This trow this error:
Uncaught TypeError: window[foo] is not a constructor
One possibility is to use eval.
class Foo {
constructor() {
console.log('Foo!');
}
};
const foo = 'Foo';
const bar = eval(`new ${foo}()`);
console.log(bar);
You will have to evaluate the safety of using eval() in your particular circumstances. If you know the origin of the string you are inserting into the code that you run eval() on or you can sanitize it first, then it may be safe.
I personally would prefer a lookup table. If you have a known number of classes that you want to map by string, then you can make your own lookup table and use that. This has the advantage of there can be no unintended consequences if the string has weird stuff in it:
class Foo {
constructor() {
console.log('Foo!');
}
};
class Goo {
constructor() {
console.log('Goo!');
}
};
// construct dict object that contains our mapping between strings and classes
const dict = new Map([
['Foo', Foo],
['Goo', Goo]
]);
// make a class from a string
const foo = 'Foo';
let bar = new(dict.get(foo))()
console.log(bar);
If you were really going to go this route, you may want to encapsulate it in a function and then add error handling if the string is not found in the dict.
This should be better than using the global or Window object as your lookup mechanism for a couple reasons:
If I recall, class definitions in ES6 are not automatically put on the global object like they would with other top level variable declarations (Javascript trying to avoid adding more junk on top of prior design mistakes).
So, if you're going to manually assign to a lookup object, you might as well use a different object and not pollute the global object. That's what the dict object is used for here.
Similar to #jfriend00 ...
const className = "Foo";
const dynamicConstructor = {};
dynamicConstructor[className] = class {
constructor() {
console.log('Foo!');
}
};
const fooInstance = new dynamicConstructor[className]();
console.log(fooInstance);
A sort of factory class constructor can also be used
const classFactory = (_className) => {
let dynamicConstructor = {};
dynamicConstructor[_className] = class {
constructor(_code) {
this.code = _code;
console.log(`${_className} initialised with code: ${_code}!`);
}
};
return dynamicConstructor[_className];
}
const MyClass = classFactory("Foo");
let fooInstance2 = new MyClass(123);
console.debug(fooInstance2);
There are good solutions but I think we need a little bit more of theory
This questions is the best example to use a Factory Pattern
Design patterns can make your code more flexible, more resilient to change and easier to maintain.
Teo, is possible to use Factory Patterns in JavaScript?
Yes, my young Padawan.
But, what is the Factory pattern?
The factory pattern is a creational design pattern, which means it deals with object creation. There are three theoretical types of factory patterns:
Simple factory
Factory method
Abstract factory
Simple Factory is an object which encapsulates the creation of another object. In ES6 it could be a constructor being instantiated by new in a function and then return the instance like this:
class Player {...}
const PlayerFactory = {
makePlayer: (type, level) => new Player(type, level),
}
In this example makePlayer returns the instance of the Player class.
Factory Method defines one method for creating a class instance, which is overridden by subclasses who decide what to return.
In ES6 it could be implemented extending classes because there are no interfaces and using a method to instantiate and object using new
class Dragon {...}
class Snake {...}
class Player {
fightMonster() {
const monster = this.makeMonster()
monster.attack()
}
}
class Warrior extends Player {
makeMonster() {
return new Dragon()
}
}
class Knight extends Player {
makeMonster() {
return new Snake()
}
}
In this example, Player class call makeMonster method then Warrior and Knight classes override makeMoster method to return either a Dragon or a Snake class instance.
Finally, the Abstract Factory provides an interface for creating families of related or dependent objects without specifying their concrete classes
The interpretation of families could be like a category or list of classes. In ES6 it can be an instance that encapsulates a group of individual factories with a common goal. It separates the details of implementation of a set of objects from their general usage.
class WinButton {
constructor(options) {
this.name = 'WinButton'
this.options = options
}
}
class LinuxButton {
constructor(v) {
this.name = 'LinuxButton'
this.options = options
}
}
// Mapping names and class definitions
const supportedOS = new Map([
['Windows', WinButton],
['Linux', LinuxButton]
])
// Use a Factory Abstract pattern
const classFactory = (c, v) => {
const maker = supportedOS.get(c)
return new(maker)(v)
}
// Factory a class from a string
const name = 'Windows'
const param = {enabled: true}
const btn = classFactory(name, param)
console.log({btn})
In this final example we can see that the function classFactory uses the Factory Abstract pattern because instantiate a class from a list of supported OS (Linux or Windows) by setting maker with the constructor of the desired class, in this case WinButton class.
I am wondering how I can augument a commonjs module from another module that requires it.
Let's assume that I have three files, two commonjs modules as below:
my-example-module.js
function MyExampleModule(){}
MyExampleModule.prototype = {
bindings: {
some: 'random',
prop: 'values'
}
}
module.exports = MyExampleModule;
another-example-module.js
var MyExampleModule = require('./my-example-module');
function AnotherExampleModule(){}
AnotherExampleModule.prototype = {
getMyExampleModuleBindings: function(){
var module = new MyExampleModule();
return module.bindings;
}
}
module.exports = AnotherExampleModule;
app.js
var MyExampleModule = require('./my-example-module');
var AnotherExampleModule = require('./another-example-module');
//modify?!?
var anotherExampleModule = new AnotherExampleModule();
console.log(anotherExampleModule.getMyExampleModuleBindings());
So what I want to do is have //modify?!? be some kind of code that will alter the original MyExampleModule prototype so when anything else attempts to require MyExampleModule it will get the modified version.
A concrete question would be - what should I replace //modify?!? with so I get logged out the the console with the assumption that my-example-module.js is read only.
{
some: 'random',
prop: 'values',
added: 'binding'
}
If you want to do this in nodejs/iojs, this is pretty simple. When node imports a CommonJS module (let's call it A), it creates an internal object.
When another module (B) wants to load the same module (A), it just gets the reference to that internal object. So if you change something on MyExampleModule in app.js it is also applied to the MyExampleModule in another-example-module.js:
app.js
var MyExampleModule = require('./my-example-module');
var AnotherExampleModule = require('./another-example-module');
//modify:
MyExampleModule.prototype.bindings = {
some: 'random',
prop: 'values',
added: 'binding'
};
var anotherExampleModule = new AnotherExampleModule();
console.log(anotherExampleModule.getMyExampleModuleBindings());
Since you create a new instance of MyExampleModule in another-example-module.js after you call MyExampleModule.prototype.bindings = {...} in app.js, the new instance will already be created with the modified .prototype.
While I haven't tested this in browserify, it certainly works in webpacks implementation of CommonJS as well.
Check out the working example on runnable (app.js is called server.js):
http://code.runnable.com/VZPdN5k65gE5vUIz