JS: How to share static class properties between imports - javascript

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.

Related

Webpack bundle interface implementation depending on environment variable

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?

Scope import for an instance only

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.

How to disable Tree shaking in rollupjs

I am trying to bundle together several javascript files using RollUp.js but when I do, the classes that aren't used get removed. This process is called tree shaking and I want to disable it.
I have found this but it doesn't seem to have any effect.
// rollup.config.js
let configuration = {
output: {
format: 'es',
},
name: 'namename',
input: './main.js',
treeshake: false, // <-- disabling tree shaking?
};
export default configuration;
I added treeshake: false to the configuration, but it doesn't seem to have any effect. Is this supposed to be placed somewhere else?
Here are the files I am trying to roll up.
// Base.js
export default class Base {
aMethod() {
return "Hello";
}
}
// main.js
import Base from './Base.js';
So with this set up, I call rollup --config and it produces something empty. So clearly, tree shaking is happening and it is removing the Base class even though I imported it.
So far the only workaround I've found is to actually create an instance of the class, which is undesirable.
// main.js
import Base from './Base.js';
export default function () {
{
new Base();
}
}
My goal is to use the bundled javascript file with JSContext. It will take in the javascript as a string and then from there, I'd invoke methods as needed.
// suppose rollup.js produces a file called "product.js"
let s = String(contentsOfFile: "path/to/product.js")
let context = JSContext()!
context.evaluateScript(s)
context.evaluateScript("var b = new Base()")
context.evaluateScript("b.aMethod()")
But because of the tree shaking the Base class never gets placed in product.js
Is there a way to disable tree shaking?
I've included a sample project for this.
Your entry file — main.js — needs to export any classes or other values that need to be accessible to the outside world:
// main.js
import Base from './Base.js';
import SubB from './SubB.js';
import SubA from './SubA.js';
export { Base, SubA, SubB };

Using namespace spread over multiple module files in TypeScript

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';

How do dynamically instantiate using typescript's external modules

We started using requirejs for our project and converting all our internal modules to external modules.
I'm having problems dynamically instanciating classes. We need to be able to instanciate classes using a simple dot notation string.
When all our modules were internal, they were accessible through the window object. Here's an example :
// file - TestAction.ts
module cb.action {
export class TestAction {
constructor() {}
}
}
Using the following function with className = "cb.action.TestAction" we could easily get a TestAction instance.
var instantiateByName = function (className) {
"use strict";
var ClassName2 = className.split('.').reduce(function (current, name) {
return current[name];
}, window);
return new ClassName2();
};
Now that we changed all our modules to external modules the TestAction.ts file now looks like this :
// file - TestAction.ts
export class TestAction {
constructor() {}
}
2 main problems :
- Depending on the dot notation string I'm receiving, I need to import a different file. ( dynamically require a file )
- I need an equivalent to the instantiateByName function above since I can't use the window object anymore.
Looking forward to having your inputs
I would slightly change the module structure (if you are using one class per file...)
class TestAction {
constructor() {}
}
export = TestAction;
And then instead of using a method to dynamically load modules by name, you can use RequireJS iteself to do it...
// Instead of 'instantiateByName'...
import TestAction = require('./action/TestAction');
var myAction = new TestAction();
This is compiled into the following...
define(["require", "exports", './action/TestAction'], function(require, exports, TestAction) {
var myAction = new TestAction();
});
And this handles the fact that RequireJS loads the script file asynchronously.

Categories

Resources