I'm new to TypeScript and the more I read about modules and namespaces, the more it confuses me. Should I go with modules? Should I go with namespaces? should I use both? help!
I have existing javascript (.js) files that I'm trying to convert to TypeScript.
There is one .js file with some general functions and one .js file with some functions specific to filters.
Now I would like to organize this a bit more with TypeScript, as I would normally do with C#.
Is this correct usage or how should it be organized?
I'm not using a module, should I? (how?)
Company.ts
namespace Company {
// nothing yet, but in future it might.
}
Company.Project.ts
namespace Company.Project {
import Company; // like this?
let myVar : string = "something";
export function handyGeneralFunction1(foo, bar) {
// ...
}
export function handyGeneralFunction2(foo, bar, foo, bar) {
// ...
doInternalCalc();
// ...
}
export function handyGeneralFunction3() {
// ...
}
function doInternalCalc() {
// ...
}
}
Company.Project.Filter.ts
namespace Company.Project.Filter {
import Project = Company.Project; // like this?
export function initializeFilter() {
// ...
initMetadata();
// ...
}
function initMetadata() {
// ...
Project.handyGeneralFunction3();
let helper : FilterHelper = new FilterHelper("aaaa,bbbb");
let res:string[] = helper.parseData();
}
function foo() {
// ...
let x :string = Project.myVar + " else"; // can I use myVar here?
}
// a class in the namespace
export class FilterHelper {
data: string;
constructor(theData: string) {
this.data = theData;
}
parseData() : string[] {
// ...
return new Array<string>();
}
}
}
If you have the possibility to really improve the project structure, you should definitely go with modules instead of namespaces. Depending on the size of the project, this can require some effort.
Introducing namespaces could be useful if your app is not that large and you don't want to invest in switching to a different build system that can handle real modules. Using namespaces is not much more than some named global variables that contain the functions and variables. This can get confusing pretty soon as you don't have any proper dependency structures. You need to take care of importing all files in the correct order for namespaces to work correctly. The import syntax you used can even be omitted, as you anyway only reference another global object, that needs to be initialized at that point in time.
So namespaces could be your very first step if you don't have the possibility to do any larger changes on your codebase. For a future-proof setup, I would definitely suggest to use real modules. But be aware that this doesn't come for free.
For further information, I suggest to have a look at the official comment on this at https://www.typescriptlang.org/docs/handbook/namespaces-and-modules.html or the section in TypeScript Deep Dive at https://basarat.gitbooks.io/typescript/content/docs/project/namespaces.html
Related
I'm converting old javascript into ES6 using babel and webpack.
In multiple files I have something like
var BLOG = BLOG || {};
BLOG.myClass1 = ...
and in another file
var BLOG = BLOG || {};
BLOG.myClass2 = ...
So, I've added export {BLOG} to these files, but how can I import BLOG multiple times? It seems it is not allowed, while I'd like to do something like
import {BLOG} from 'file1.js'
import {BLOG} from 'file2.js'
and have myClass1 and myClass2 into BLOG.
Is there a way to do this?
So, I've added export {BLOG} to these files, but how can I import BLOG multiple times?
You'd have to use different import bindings for them:
import {BLOG as BLOG1} from 'file1.js';
import {BLOG as BLOG2} from 'file2.js';
...then use BLOG1 and BLOG2. Or if that bothers you, add
const BLOG = Object.assign({}, BLOG1, BLOG2);
after the imports and keep using BLOG.
If you have cyclic dependencies, it's possible that BLOG1 and BLOG2 may not be fully-populated right away. With true import/export, in that situation, the objects you receive will have their properties, but those properties won't be initialized yet. So with true import/export or a good simulation, you could use accessor properties to handle it:
// In a true ES2015+ module environment, or a simulated one that
// creates the properties up-front like the real one does
const BLOG = (() => {
const b = {};
for (const blog of [BLOG1, BLOG2]) {
for (const key of Object.keys(blog)) {
Object.defineProperty(b, key, {
get(name) {
return blog[name];
}
});
}
}
return b;
})();
(Obviously, you'd wrap that in a function and reuse it.)
In a simulated module environment that doesn't create the properties up-front like the real one would, you could use a proxy (though of course, if you're going to run this in a pre-ES2015 environment, it won't have Proxy):
const BLOGS = [BLOG1, BLOG2];
const BLOG = new Proxy({}, {
get(target, propName, receiver) {
const b = BLOGS.find(b => propName in b) || target;
return Reflect.get(b, propName, receiver);
}
});
Then, properties added to BLOG1 and BLOG2 after-the-fact still get resolved correctly.
All of which is an awful lot of bother just to avoid the search-and-replace mentioned next.
But: Instead of exporting BLOG, as SLaks says, export the classes, and import those. There's no need for a "namespace object" when you're using proper modules.
export { myClass1 };
and
export { myClass2 };
and then
import { myClass1 } from "file1.js";
import { myClass2 } from "file2.js";
You can even export the class as you define it, either:
export function myClass1() { /* ... */ }
or if using class notation:
export class myClass1 { /* ... */ }
Changing new BLOG.myClass1 to new MyClass1 across multiple files is a really simple search-and-replace. On *nix, you could throw sed at it and do an entire directory tree...
Side note: The overwhelming convention in JavaScript is that constructor functions (often loosely called "classes," e.g., functions you use with new) are written with an initial upper case character. E.g., MyClass1 and MyClass2.
You need to use import in the following manner
{BLOG as variable name} from file
I'm just getting into using es 6 modules for front end development (we don't use Node at all), and am wondering if this pattern we've come up has any pitfalls or if you have any improvement suggestions. I guess it uses some of the rationale behind the revealing module pattern, in es6 modules. I ask this question because most es6 module "how to guides" I've seen do something different, which I'll note at the very bottom of the question.
Some things to note:
We (are pretty sure we) want each module to only export one thing. This is listed as a best practice in the Airbnb style guide, and we've just found it nice overall when consuming npm packages
We really like naming methods with "public" and "private" (guess we should be using _ for private methods as that's newer best-practice), it makes it easy to see what is available outside of the module
module.js:
// publicly available method
function publicHello() {
return 'Hello';
};
// publicly available method
function publicHelloWorld(){
const a = publicHello();
const b = privateProcessWorld(a);
return b;
};
// private method
function privateProcessWorld(x) {
return x + ' world';
};
// create an object to export, containing only the public methods
// note that we rename them here as well, making them easier to consume
const exp = {
h: publicHello,
hw: publicHelloWorld,
};
// export the object as default so it can be used in an unnamed import
export default exp;
to consume the module:
import whatever from "module.js";
whatever.h(); // "Hello"
whatever.hw(); // "Hello world"
What I have seen in most "es6 module how to" guides is this:
var utils = {
generateRandom: function() {
return Math.random();
},
sum: function(a, b) {
return a + b;
}
};
export default utils;
We (are pretty sure we) want each module to only export one thing.
No. Don't do this. If your module provides multiple functionalities, like a bunch of helper functions, and does not provide a single function or single class or something, you should also export multiple things.
Just change your default export to
export {
publicHello as h,
publicHelloWorld as hw,
}
and your import to
import * as whatever from "module.js";
There is a well-known approach in node.js to leverage the module factory pattern. For, example:
m.js
function factoryMethod(module) {
// doing some stuff and returning object initialized with module parameter
}
app.js
var o = require('./m')(module);
// using o
How can I do the same in typescript. Actually, creating m.ts is not a problem:
m.ts
function factoryMethod(module: NodeModule): any {
// doing some stuff
}
export = factoryMethod;
But how should I use import syntax to use this module factory method like in javascript?
I'm not sure I quite get this common pattern. You're passing the module object from one module into another? All kinds of other objects are passed around like this, (e.g. app, db), but I don't like the idea of passing around the module object. I'd be tempted to call it an anti-pattern. Surely the module object should stay in the module to which it belongs.
That said, if you just want to import and call a function on the same line, you could do this using Node's require() function, just like regular JavaScript. Let's say you are passing an Express app rather than module.
const o = require('./m')(app);
However, you lose type-safety with this; o will be of type any. You would have to explicitly define the type of o.
const o: Module = require('./m')(app);
This is a bit silly. In fact, Module is likely to be defined in the module you are requiring anyway, so it is likely also self-defeating. My suggestion is this. Don't expect to use the same patterns you are used to in plain JS in TypeScript, which has its own patterns.
One thing you could do is import the function at the top, and then call it later. TypeScript uses ES2015-style modules, which don't allow you to import a function and call it on the same line. You will have to rewrite both files, since export = is not valid in ES2015.
// m.ts
interface Module {
// properties, methods, etc.
}
export function factoryMethod(app: Express.Application): Module {
let module = {};
// Initialize module methods, properties, etc.
return module;
}
The interface allow type inference in app.ts, which is an improvement of a kind.
// app.ts
import {factoryMethod} from './m';
// ...
let o = factoryMethod(app);
But this is still very silly. We don't need to define an interface and all that nonsense. Instead, we can use a class. Classes are very often the answer in TypeScript, and you'll find that most patterns in TypeScript involve them.
// m.ts
export class Module {
constructor(private app: Express.Application) { }
cache: string[];
someMethod(): Promise<Express.Response> {
// do something with this.app
}
}
And then in app.ts
import {Module} from './m';
// ...
let o = new Module(app);
Now we don't have to worry about interfaces and all that. The class itself is a type. This is quite a bit different from what you are likely to see in a typical Node app, but it is the sort of pattern you find all the time in TypeScript.
Hopefully that gives you some ideas.
import {factoryMethod} from './m.ts'
let module = factpryMethod('module');
I know this question is fairly old but I came across the same issue when converting an existing JS project to TypeScript that makes use of this pattern.
Instead of having to pre-define the module definition in an interface we can use the ReturnType helper to get a type definition of the return type of a function!
This is what the module might look like:
function initModule(arg1: number, arg2: number) {
function functionA() {
return arg1;
}
function functionB() {
return arg2;
}
return {
functionA,
functionB,
};
}
export default initModule;
export type SampleModule = ReturnType<typeof initFactory>;
And here is how we could use it:
import initModule, { SampleModule } from './factory';
const newModule: SampleModule = initModule(1, 2);
newModule.functionA(); // 1
newModule.functionB(); // 2
Seriously, how cool is TypeScript? :)
I use Mocha/Chai for testing JavaScript front-end code and now we switched to TypeScript. I have several functions I want to test. But they shouldn't be exportable. Can I get an access to this functions and test it without adding export to them?
There is no way to access a non-exported module function.
module MyModule {
function privateFunction() {
alert("privateFunction");
}
}
MyModule.privateFunction(); // Generates a compiler error
However, leaving aside the question of validity of private method testing, here is what you can do.
Group your functions into an utility class, and then leverage the fact that private class members can be accessed via square bracket notation.
module MyModule {
export class UtilityClass {
private privateFunction() {
alert("privateFunction");
}
}
}
var utility = new MyModule.UtilityClass();
//utility.privateFunction(); Generates a compiler error
utility["privateFunction"](); // Alerts "privateFunction"
While it is not possible to access non-exported function directly there is still a way to export them in a "semi-hidden" way. One possible approach would be:
// In your library module declare internal functions as non-exported like normal.
function someInternalFunctionA(x: number): number {
return x;
}
function someInternalFunctionB(x: number): number {
return x;
}
// At the bottom, offer a public escape hatch for accessing certain functions
// you would like to be available for testing.
export const _private = {
someInternalFunctionA,
someInternalFunctionB,
};
On test side you can do:
import { _private } from "./myModule";
test("someInternalFunctionA", () => {
expect(_private.someInternalFunctionA(42)).toEqual(42);
});
What I like about the approach:
No need to mark someInternalFunctionA with export directly.
It should still be very obvious that the stuff under _private isn't officially part of the public interface.
As you can see in related questions the issue of testing private functions inside classes or modules is heavily debated on StackOverflow - The following might be an architectural solution to not even have that discussion:
If these functions feel important enough to be tested separately, but should not be accessible as part of a module, should they maybe be placed in their own module?
This would solve your issue of accessibility - they now are public functions in one module, and you can easily use them from within another module and not expose them as part of that module.
Best solution I found is to export the private function under different name so this name will remind you not to use this function anywhere else.
export const getPrice__test = getPrice;
function getPrice(): number {
return 10 + Math.Random() * 50;
}
But they shouldn't be exportable. Can I get an access to this functions and test it without adding export to them?
In general, no. It's possible to access private class members, but not unexported members of modules.
I would echo #Katana314's comments -- unit tests should not be concerning themselves with non-public methods. Trying to do so is an indication that you're testing the implementation details of the module rather than the contract the module claims to implement.
This is a total hack, but hey...
window.testing = {};
Then in your module:
module.exports = {
myPublicFunction: myPublicFunction
};
window.testing.myModule = {
myPublicFunction: myPublicFunction,
myPrivateFunction: myPrivateFunction
};
I'll preface this question by saying I'm using Intellij IDEA.
Following this question: If external typescript modules don't have a module name, how do you avoid naming conflicts?
This is all fine and well, but let's say I'm have two Rectangle classes in two Rectangle.ts files, but in different packages, say src/utils/geom and src/utils/ui, or something like that.
src/utils/geom/Rectangle.ts has calculateSurface() as its only method and src/utils/ui/Rectangle.ts has display() as its only method.
Now if I call them both in a single file, I'll get both methods as possible calls in the type-hinting.
import GeomRectangle = require();
import UiRectangle = require();
var geom: GeomRectangle = new GeomRectangle();
var ui: UiRectangle = new UiRectangle();
// Now both those are valid
ui.calculateSurface();
ui.display();
I'm thinking it's because I'd have both Rectangle.ts files have a exports = Rectangle, since that's the name of the class and Intellij IDEA must use this export statement to determine what it'll suggest to you.
Am I wrong in my assumption? And is there any way to have type-hinting that won't trip on itself when using external modules with, sometimes, classes having the same name as each other?
I tried your code in Visual Studio:
geom/Rectangle.ts
class Rectangle {
calculateSurface() {
console.log("a");
}
}
export = Rectangle;
ui/Rectangle.ts
class Rectangle {
display() {
console.log("b");
}
}
export = Rectangle;
Test.ts
import GeomRectangle = require("./geom/Rectangle");
import UiRectangle = require("./ui/Rectangle");
var x: GeomRectangle = new GeomRectangle();
var ui: UiRectangle = new UiRectangle();
// Now both those are valid
ui.calculateSurface(); // compilation error here
ui.display();
There is no calculateSurface method in the intellisense options of the ui object.
There is compilation error on the ui.calculateSurface() line
So it probably could be bug related to Intellij IDEA or configuration issue.