In a TypeScript application that uses Webpack for bundling, we have an interface such as:
interface IMyInterface {
method(): void
}
And two implementations which require different dependencies.
import IMyInterface
import package-for-foo;
class FooImplementation implements IMyInterface {
method(): void { ... }
}
import IMyInterface
import package-for-bar;
class BarImplementation implements IMyInterface {
method(): void { ... }
}
My idea is to have two different target bundles, one containing FooImplementation with package-for-foo dependency, and other bundle containing BarImplementation containing package-for-bar dependency.
Additionally, when running, the implementation of which IMyInterface would be used would be resolved at initialization (as it cannot change) and be a global variable accross the application, so clients could simply do impl.method() regardless of which implementation was initialized.
I am able to somewhat accomplish the running part by using an envvar and with webpack DefinePlugin creating a global variable. Then using this global variable at initialization to instantiate the proper implementation. After resolving the global variable, I'll do:
ImplControl.ts file
import FooImplementation from "./FooImplementation ";
import BarImplementation from "./BarImplementation ";
let impl: IMyInterface;
if (globalVar === "foo") {
impl = new FooImplementation ();
} else {
impl = new BarImplementation ();
}
export default impl;
However, they are all being bundled together still since they are somewhat coupled by the imports. Is there any way to make this happen as I want to?
Related
so I am running into this problem while I am converting some old js to new es6 imports (used in the browser).
The problem:
The static properties of a class are not shared between the various imports in different files.
(Even though they are bundled into one file eventually (using Vite)).
A simplified version of the code:
// scripts.ts (will be three-shaked, compiled and bundled using Vite)
// This file will be loaded in a simple index.html
import Base from "./Base";
import Form from "./Form";
window.Base = Base;
window.Form = Form;
document.addEventListener("DOMContentLoaded", () => {
Base.setRoot("myroot");
console.log(Base.root); // 'myroot'
Form.logRoot(); // 👈 'default', but expected 'myroot'
});
// base.ts
export default class Base {
static root: "default"
static setRoot(root) {
this.root = root;
}
}
// form.ts
import Base from "./Base"
export default class Form {
static logRoot() {
// attempt 1
console.log(Base.root); // doesn't work
// attempt 2
console.log(window.Base.root); // works, but undesirable: TS throws error that window.Base is undefined
}
}
The question:
So how can I share the static properties of this Base class between the various imports, without having to pull it from window.Base?
Perhaps a little background:
These classes used to be just two global var objects, that got bundled together, without using any imports or anything. Thus the state of the vars would be shared because they basically just lived on the window.
I am trying to implement a global module in nest.js
I have created a service like below
export interface ConfigData {
DB_NAME: string;
}
#Injectable()
export class ConfigManager {
private static _instance?: ConfigManager;
public static configData:ConfigData | null;
private constructor() {
console.log(ConfigManager)
if (ConfigManager._instance)
ConfigManager._instance = this;
else{
ConfigManager._instance = new ConfigManager()
ConfigManager.configData = <ConfigData|null><unknown>ConfigManager.fetchSecretData()
}
}
private static async fetchSecretData():Promise<ConfigData|null>{
// some data from db
}
// static get instance() {
// return ConfigManager._instance ?? (ConfigManager._instance = new ConfigManager());
// //return ConfigManager._instance ?? (ConfigManager._instance = ConfigManager.fetchSecretData()) //new ConfigManager());
// }
}
configuration.module.ts
#Global()
#Module({
providers: [ConfigManager],
exports: [ConfigManager],
})
export class ConfigurationModule {}
and in app.module.ts added ConfigurationModule in imports.
Also adding private constructor on service unable it to add in module.ts file.
I am expecting that I should be able to configData anywhere without importing the ConfigManager. but it's not working...
ConfigManager is not available without import.
You've only marked your module with #Global decorator, but the NestJS needs to somehow initialize that module and make it globally available.
What this means is that you have to add this module to your core application module and NestJS will do the rest for you, so something like this (or however your root module is named):
#Module({
imports: [ConfigurationModule],
})
export class AppModule {}
From the documentation
The #Global() decorator makes the module global-scoped. Global modules
should be registered only once, generally by the root or core module.
Global modules in nest only means you don't have to include that module in the imports: [] of every other module that requires it's providers. The providers in the global module still behaves as as normal providers i.e. you need to inject it where you need it.
So in your case since you have already added #Global() to ConfigManager and imported ConfigurationModule in app.module.ts, you will not need to add ConfigurationModule in imports for any other modules who want to use ConfigManager. You would, however, still need to inject the ConfigManager provider - that's what #Injectable() means :).
To do the injection, you need a constructor(private configManager: ConfigManager) {} in the class that needs to use ConfigManager, and since you need access to the type of the class, you'll need import { ConfigManager } from '../path/to/ConfigManager' as well.
this is 'per-design' of es6/ts: you cant use the class without importing it.
you are mixing the concepts of di (instantiation/composition) with importing (defining which classes are available in the module scope)
Good evening to everyone.
I'm not sure how can I explain my issue. I will show it to you by showing examples of the code and expected results. I could not use code from the real issue because the code is under license. I am very sorry for that and I will be glad of someone can help me solve my issue.
I'm using latest version of webpack, babel.
My application is spliced to three parts what are dynamically imported by each other. It is mean if I run split chunks plugin it will really create three clear files.
The parts are Core, Shared, Application. Where the Core only creating an instance of the application.
Result of the parts is bundled to single file. So it is linked by one html's script tag.
Project structure is:
src/app // For Application
src/core // For Core
src/shared // For Shared
In webpack configuration I am resolving alias for import ˙Editor$˙.
I renamed naming of variables because they are including project name.
resolve: {
alias: {
"Editor$": path.resolve('./src/app/statics/Editor.js'),
}
},
The content of Core file is
function createInstance(name, id) {
import("app").then(App => {
App(name, id)
});
}
The little bit of Application file is
imports...
import Framework from "./framework"
function createApp(name, id) {
new Framework({name, id}).$mount(...)
}
export default createApp
In the Application classes (what are instantiated inside Framework)
Is this import
import Editor from "Editor"
The Editor class is a singleton. But only for created instance.
class Editor {
static instance;
id = null;
constructor(){
if(this.constructor.instance){
return this.constructor.instance
}
this.constructor.instance = this
}
static get Instance() {
return this.instance || (this.instance = new this())
}
static get Id {
return this.Instance.id;
}
}
export default Editor
The issue is webpack dependency resolving. Because webpack puts and unify all imports to the top of the file.
So the imports are evaluated once through the life-cycle of the program.
But I need to tell webpack something like: There is an instance creation. Declare the new Editor singleton for this scope. Don not use the already cached one.
My another idea how to fix this is to set context for the instance. And in the Editor singleton create something like new Map<Context, Editor> if you get what I mean. But I did not find a way how to set a context for an instance or scope the import only for it.
I will appreciate any help. I am googling two days and still no have idea how to do it without rewriting all the imports.
Sorry for bugs in my English. I am not native speaker and my brain is not for languages.
Thanks everyone who take look into my issue.
How about recreating the Editor:
// Editor.js
class Editor {
// ...
}
let instance;
export function scope(cb) {
instance = new Editor();
cb();
instance = null;
}
export default function createEditor() {
if(!instance) throw Error("Editor created out of scope!");
return instance;
}
That way you can easily set up different scopes:
// index.js
import {scope} from "./editor";
scope(() => {
require("A");
require("B");
});
scope(() => {
require("C");
});
// A
import Editor from "./editor";
(new Editor()).sth = 1;
// B
import Editor from "./editor";
console.log((new Editor()).sth); // 1
// C
import Editor from "./editor";
console.log((new Editor()).sth); // undefined
// note that this will fail:
setTimeout(() => {
new Editor(); // error: Editor created out of scope
}, 0);
That also works for nested requires and imports as long as they are not dynamic.
I'm rebuilding the Atlassian React+Flux tutorial in TypeScript (using bindings of React, Flux and Node from tsd) but I'm encountering a problem with implementing the dispatcher, which essentially is a singleton.
I'm trying the following:
AppDispatcher.ts
export var instance = AppDispatcher.getInstance();
class AppDispatcher extends Flux.Dispatcher<AppPayload> {
private static _instance:AppDispatcher = new AppDispatcher();
constructor() {
super()
if(AppDispatcher._instance){
throw new Error("Error: Using this constructor should not be possible...WTH javascript");
}
AppDispatcher._instance = this;
}
public static getInstance():AppDispatcher
{
return AppDispatcher._instance;
}
...
}
Like in the tutorial I use a JSON file on localStorage to fake a web API. So my ActionCreator does the following:
ActionCreator.ts
import AppDispatcherInstance = require('../dispatcher/AppDispatcher');
//ActionCreator
//In Flux ActionCreators
export function receiveAll(rawNodes:any) {
AppDispatcherInstance.instance.handleServerAction(AppDispatcherInstance.ActionIdentifier.REFRESH_FROM_SERVER, rawNodes)
}
Unfortunately I get a runtime exception Uncaught TypeError: Cannot read property 'getInstance' of undefined pointing to the transpiled Javascript line
exports.instance = AppDispatcher.getInstance();
Am I implementing the singleton wrong in TypeScript?
Is there a better way in general to do this? I thought about not writing a class at all and to the singleton by only exporting certain functions (like recommended in other SO questions) but I still want to extend the Flux.Dispatcher class?
Here's how I've been using singletons with modules in a React/Flux project:
class AppDispatcher extends Flux.Dispatcher {
// ...
}
export default new AppDispatcher();
The singleton can be imported in another file like:
import AppDispatcher from "./AppDispatcher";
AppDispatcher.dispatch(...);
If you need the class type exported, you can add export to class AppDispatcher and import the singleton by a different name if you need both in the same file (I tend to need this in unit tests):
import AppDispatcherInstance, {AppDispatcher} from "./AppDispatcher"
As the commentor suggests, you need to move code that consumes a class below the declaration of that class. Classes are just variables, and variables in JavaScript get initialized in order.
In general, you shouldn't need to use the explicit class singleton pattern in TypeScript. See How to define Singleton in TypeScript
Useless singleton pattern
class Singleton {
/* ... lots of singleton logic ... */
public someMethod() { ... }
}
// Using
var x = Singleton.getInstance();
x.someMethod();
Namespace equivalent
namespace Singleton {
export function someMethod() { ... }
}
// Usage
Singleton.someMethod();
var x = Singleton; // If you need to alias it for some reason
I've started work on a large-scale typescript project.
Right from the outset, I want to keep my files organized (this project will be split between lots of developers so order is very necessary).
I have been attempting to use modules / namespaces and splitting classes out into separate files for each one, with a folder holding the namespace.
The file structure is:
app.ts
\Classes
---- \Animals
---- ---- Mammals.ts
---- ---- Reptiles.ts
I then attempt to import all files in that namespace in app.ts using something like: import * as Animals from "./Classes/Animals"
As for the namespace files themselves, I have tried the following, with no success:
namespace Animals {
export class Mammals {
constructor() {
}
}
}
and also:
module Animals {
export class Reptiles {
constructor() {
}
}
}
Unfortunately, the path is never recognized (as it points to a folder and not a single file). Is this even possible? Having all my classes from a single namespace in one file will result in files which are thousands of lines long and for this project that is not maintainable.
I have also noticed that TypeScript 1.5 has support for tsconfig.json - however, having to add each file manually to the map is a sure-fire way of introducing issues when developers start adding classes.
NOTE: I'm using Visual Studio 2015, TypeScript 1.5 (I believe, not sure how to verify). I also have ES6 support turned on.
Use re-exporting to create an external module that groups and exposes types from other modules:
// Classes/Animals.ts
export * from '.\Animals\Mammals';
export * from '.\Animals\Reptiles';
Then import the types from the new module as usual:
// app.ts
import * as Animals from '.\Classes\Animals'
let dog: Animals.Dog;
let snake: Animals.Snake;
Or
// app.ts
import { Dog, Snake } from '.\Classes\Animals'
let dog: Dog;
let snake: Snake;
Found a way to achieve your goal but not with the namespace keyword.
The "Animals" classes, Animal.ts & Mammal.ts & Reptile.ts under namespace.
with index.ts for the barrel.
animals.ts for namespace grouping.
Sample Classes:
index.ts (as barrel)
animals.ts (for namespace grouping)
And here you go of the concept of the namespace.
If you have your own library and you want to export the multiple files like from namespace, you can do this:
// classes/animals/mammals.ts
export enum Mammals {
cow = 'cow',
goat = 'goat',
}
// classes/animals/reptiles.ts
export interface Reptile {
isOurOverlord: boolean;
}
export function isOurOverlord(specimen: Reptile) { ... }
// classes/animals/index.ts
import * as mammals from './mammals';
import * as reptiles from './reptiles';
export { mammals, reptiles };
// classes/index.ts
import * as animals from './animals';
export { animals };
// app.ts
import { animals } from './classes';
const reptile: animals.reptiles.Reptile = {
isOurOverlord: animals.reptiles.isOurOverlord(...),
}
edit: i.e. you don't need typescript's namespaces in order to use that super convenient syntax of animals.reptiles.Reptile for types and values animals.mammals.Mammals within the same "namespace".
Seems there is no way to do this using namespaces on their own (unless you want to use Module Augmentation and declare every new item to add separately); however, a namespace can be part of a class, which can be extended! This is the best alternative I can find:
CoreLibraryTypes.ts
abstract class Types { }
namespace Types {
export class TypeA { }
export class TypeB { }
export class TypeC { }
}
export { Types };
CoreTypesExtended.ts
import CoreLibraryTypes from "./CoreLibraryTypes";
abstract class Types extends CoreLibraryTypes { }
namespace Types {
export class TypeD { }
export class TypeE { }
export class TypeF { }
}
export { Types };
The downside, of course, is that only the import of the second module will have the new types added. The first module will remain as before. Ideally it would be nice to "update" a namespace of types with additional types (like from plugins), such that module augmentation was more naturally supported (instead of having to write it by hand), but I guess that will have to do until someone realizes augmentation of modules by manually declaring updated definitions is just a half-a$$ way to do what namespaces already do lol (including classes, as seen above, which can already use namespace merging as part of the class). ;)
Note: In the example above I used export { Types }; for a reason - this will allow others to augment my modules. Augmentation is not supported for default exports (unless that is desired - sort of seals it virtually).
External modules imply that you load modules file by file. Both AMD and CommonJS do not have such thing as namespace. You can use some kind of postprocessing to bundle files in one module.
The following defines an internal module:
module Animals {
export class Reptiles {
constructor() {
}
}
}
You shouldn't use import for it. Animals.Reptiles is visible anywhere. The only aim is to load scripts in the proper order (e.g. base classes before heritors). So you should list all files in ts.config or somewhere else. In my project I use bundles on folders and have a convention to add # to filenames of base classes.
Another solution is to use external modules: AMD (RequireJS) or CommonJS (Browserify). In that case remove upper level module from declaration. If one file contains only one type you can export it as a root:
class Reptiles {
constructor() {
}
}
export = Reptiles;
You can refer module by file path:
import Reptilies = require('..\Animals\Reptilies')
var reptile = new Reptile();
Or with new ES6 modules:
export class Reptiles {
constructor() {
}
}
import { Reptiles } from '..\Animals\Reptilies';