TypeScript - Attach module/namespaces to window - javascript

I've built a lot of APIs and applications using ES2015, but I am not used to the best practices in TypeScript yet, so maybe you can help me.
Let's say I am building an API/SDK for a shop. The goal is that the user includes my js file and accesses the shop and its namespaces via the window object, much like it is possible with angular and other libs as well.
window.shop.init();
window.shop.cart.get();
window.shop.cart.set();
window.shop.cart.clear();
In ECMAScript 2015, I would write my methods like get and set, import them in my main file and extend the shop object and finally the global object.
// in my cart.js namespace file
export {get} from './get';
// in my shop.js
import * as cart from './cart';
global.shop = {
cart
}
Being a good approach for namespacing in ES2015, it feels kinda wrong in TypeScript having all those module and namespace keywords.
I basically want to achieve the same in TS. I tried things like the following, but with no success.
module shop {
export const cart = {...}
}
(<any>window).shop = shop;
or
namespace shop {
// ...
}
(<any>window).shop = shop;
There where some tutorials claiming that a module is automatically attached to the global/window object, but that did not happen for me.
I am using TypeScript 1.8.10. Any help is greatly appreciated!

There where some tutorials claiming that a module is automatically attached to the global/window object, but that did not happen for me.
Maybe because the code of your namespace is in an (ES6) module instead of in a script? The differences between scripts and modules are detailed here.
The code below makes a global variable shop in the browser if it is loaded as a script, ie with a tag <script src="shop.js"> (or you can concatenate this file with other JavaScript files, for example with uglifyjs).
// File shop.ts
namespace shop {
export const cart = { /* ... */ }
}
If your code is loaded as an ES6 module (ie with the help of Webpack, SystemJS, RequireJS or other), your solution is valid:
(<any>window).shop = shop;

The answer by #paleo is not perfect.
It just suppress the type inference for shop.
I also encounter the similar problem this morning. I tried so many "solutions" on SO, but none of them produce no type error absolutely and enable triggering type jumping in IDE(webstorm or vscode).
Finally, from here
https://github.com/Microsoft/TypeScript/issues/3180#issuecomment-102523512
, I find a reasonable solution to attach typings for global variable which acts as interface/class and namespace both.
Example is below:
// typings.d.ts
declare interface Window {
shop?: MyNamespace & typeof MyNamespace
}
declare interface MyNamespace {
somemethod?()
}
declare namespace MyNamespace {
// ...
}
Now, the code above merges the typings of namespace MyNamespace and interface MyNamespace into the global variable shop(the property of window).

Related

TypeScript Augmenting Window not working in separate files

I'm looking to add another property to window. I can do that with this:
// global.d.ts
import { IConfig } from './src/models';
export {};
declare global {
interface Window {
_env: IConfig;
}
}
But then when I try to reference this new property in a different file, it complains:
// src/util.ts
// Property '_env' does not exist on type 'Window & typeof globalThis'.
export const URL = `https://example.com/${window._env.path}`;
But when I combine these into the same file, everything is fine and there are no errors. Is there anyway I can have these in a separate file?
I'm using TypeScript 4.1.2.
I went down a bit of a rabbit hole but found this relevant documentation
I was able to accomplish this exactly as you have written (different type of course) in an existing angular 8 project and svelte project using their existing polyfills.ts files for my global declaration.
Are you sure you tsconfig.json is compiling everything correctly?

Webpack code splitting breaks instanceof between chunks

tl:dr;
class ModuleInBundleA extends ModuleInBundleC { … }
window.moduleInBundleB.foo(new ModuleInBundleA())
class ModuleInBundleB {
public foo(bar: ModuleInBundleA|ModuleInBundleC|number) {
if (bar instanceof ModuleInBundleA || bar instanceof ModuleInBundleC) {
// always false
…
}
}
}
Details:
I'm trying to start using TypeScript + Webpack 4.41.6 on the project that has mostly old codebase. Basically I want to package several small modules onto bundles to migrate softly without moving whole project onto new js stack.
I found out that Webpack can do this with code splitting, and package shared code into bundles on it's own with some configuration. However I can't really control what will be in every bundle unless I build every bundle separately and then only share types, using my own modules as external libraries and that's bit frustrating.
Maybe on this point you can say that I'm doing something wrong already and I would like to hear how can I achieve my goal of using bundles just as vanilla javascript (controlling defer/async on my own and using script tag on my own as well), and I don't really want to pack everything as an independent package with own configuration, types export and so on.
Hope you got overall context. Closer to the point.
I have the following function, that is bundled to it's own chunk called modal-manager.js.
public showModal (modal: ModalFilter|AbstractModal|number) {
let modalId: number;
console.log(modal);
console.log(typeof modal);
console.log(modal instanceof ModalFilter);
console.log(modal instanceof AbstractModal);
if (modal instanceof AbstractModal) {
modalId = modal.getId();
} else {
modalId = modal;
}
...
};
(Originally it had no ModalFilter as ModalFilter inherits AbstractModal but I included it for demonstration purposes)
The abstract modal is bundled automatically to modal-utils.js as it's shared between modules.
Next, I have another big bundle called filter.js. This one literally creates instance of ModalFilter const modalFilter = new ModalFilter(...). I think it's work mentioning that instance of modalFilter declared to the global window variable. The trouble is that filter.js calls modal.js code (through window.modalFilter.showModal(modalFilter)) with no problems whatsoever, but I see the following result of console.log:
ModalFilter {shown: false, loading: false, closing: false, html: init(1), id: 0, …}
modal.bundle.23e2a2cb.js:264 object
modal.bundle.23e2a2cb.js:265 false
modal.bundle.23e2a2cb.js:266 false
I disabled mapping to get more into code and see this:
ModalManager.prototype.showModal = function (modal) {
var modalId;
console.log(modal);
console.log(typeof modal);
console.log(modal instanceof _builder_component_modal_filter__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"]);
console.log(modal instanceof _modal_abstract__WEBPACK_IMPORTED_MODULE_0__[/* default */ "a"]);
if (modal instanceof _modal_abstract__WEBPACK_IMPORTED_MODULE_0__[/* default */ "a"]) {
modalId = modal.getId();
}
else {
modalId = modal;
}
this.modals[modalId].show();
this.scrollLock(modalId);
};
With my understanding of how javascript works, instanceof should check the object-creator function. As code chunks separated (modal.js has no same code with modal-utils.js) the creator function should be the same. However, getting more to the details I see that webpackJsonp can be really tricky and calling them from kind-of independent environments, still it should be the same environment where FilterModal, AbstractModal is called. The ModalManager could have own environment I believe. But code called is 100% the same. Could that webpackJsonp bundle-arrays be the source of the problem? If so, how can I avoid that and make modal.js bundle understand that both filter.js and others reference the same AbstractModal from modal-utils.js?
If I'm doing it wrong, is there a simple way to start bundling small and efficient scripts build with TypeScript and Webpack (or other tools)?
Also, I see the externals feature of Webpack, but haven't figured out how to use that in my case. In general, I'm ok with current set up except instanceof issue. The reason I want to avoid multiple builds is that I'll probably have dozens of smaller bundles that shared across different modules and having dozen of npm packages for each seems excessive.
Prefacing this with; I don't know the answer to the exact issue that you are facing in regards to the instanceOf part of your question. This is aimed at the "how did you do it" part.
Approx. 4 weeks ago we also changed from a .js to .ts implementation with about 1-2 hunderd .js files. Obviously we didn't want to migrate these all at once over to .ts as the effort was too high.
What we ended up doing was identifying .js scripts which needed to run on specific pages and added these into webpack as entry files. Then for all of the other suporting scripts, if we required their contents in our new .ts files, we actually created a large index/barrel file for them all, imported them in and then webpack will automatically include these in the correct scope alongside their respective .ts files.
What does this look like?
legacy.index.ts: For every single supporting .js file that we wanted to reference in any way in .ts.
var someFile_js = require("someFile.js");
export { someFile_js };
This then allowed us to import and use this in the .ts files:
import { someFile_js } from './legacy.index';
In reply to #tonix. To load a defined list:
webpack.config
const SITE_INDEX = require('./path-to-js-file/list.js')
module.exports = {
entry: SITE_INDEX
...
}
list.js
{
"filename1": "./some-path/filename1.js"
"filename2": "./some-path/filename2.ts"
}

Declaring global helper functions and understand the namespace when using laravel-mix / webpack

I am totally new to webpack (i previously built my apps by including tons of css / js files by "hand") and am now trying to understand how namespaces work when working with the named tools.
i have an app.js
require('./bootstrap');
require('./helperFunctions');
/* ... more, unrelated stuff */
webpack.mix.js is untouched from the original file delivered with the laravel 5.5 sample project
let mix = require('laravel-mix');
mix.js('resources/assets/js/app.js', 'public/js')
.sass('resources/assets/sass/app.scss', 'public/css');
my helperFunctions.js is a simple js file with some helpful functions i want to use throughout my project:
function foo_bar(A, B) {
return A - B;
}
/* more functions, following the same structure... */
but everytime i try to use one of the functions defined in the helperFunctions file i find that they are "undefined", even in the app.js file directly after the 'require' happens.
after inspecting the generated app.js file i found that my functions are encapsulated in an anonymous function function(module, exports) { /* my File contents go here */ }, resulting in them being unavailable to the rest of the scripts.
while i understand that this is propably there to reduce polluting the global namespace, i dont understand how i am supposed to define global objects (such as data Storage objects for vue) or functions such as helper functions.
can anybody explain how this is supposed to work, or link me to a ressource explaining this for someone who never worked with an asset compiler (if this is even the right terminus).
cheers
// Edit: i think i found a solution, after stumbling over this:
https://nodejs.org/api/modules.html#modules_modules
I editted the helper functions file to something like this:
module.exports = {
foo_bar(A, B) {
return (A - B);
},
/* ... more functions ... */
}
And imported it wherever i need it like this:
import HelperFunctions from './helperFunctions'
var result = HelperFunctions.foo_bar(5, 8);
However, this only works in files which are pre packed using webpack. is registering the component under window.HelperFunctions the only way to make them available in dynamically generated <script></script> tags throughout the website?
Registering your helper methods on the window object, as you kind of suggested, is a simple and easy to understand approach, so that's what I would choose to do if I wanted these methods to be available globally.
window.myHelperMethod = function () { console.log('ayo, this works!') }

Accessing Window Object from .tsx file

Typically in my .ts files I can access the window object by calling something such as:
(<any>window).myObject
I am getting compilation errors for this in my .tsx files. Is there any way I can access it from a .tsx file?
Thanks.
You can use the as syntax for type assertion. This is the alternate syntax for type assertion as <type>obj conflicts with JSX syntax:
(window as any).myObject
The above will work, however if you want strong typing consider augmenting the Window interface to add your property so you will get compile-time type checking:
declare global {
interface Window {
myObject: YourObjectType;
}
}
The syntax <type> is being deprecated by the ts team. This is because there is too much ambiguity between it and the new jsx syntax. Instead the ts team introduced the as operator for type assertions.
So this syntax:
(window as any).things
is more update to date.
This change was done, because basically, its very tough to tell a compiler when is such a syntax relating to a type or a jsx-element. Likewise the notation becomes much harder to read (see below for example):
<Component>
{<String>something}
</Component>
Some more detail can be found here https://basarat.gitbooks.io/typescript/docs/types/type-assertion.html#as-foo-vs-foo and here https://github.com/Microsoft/TypeScript/issues/296
Further Explanation on #Saravana Answer,
best way is to have this defined in your types folder, and add a definition file with .d.ts extention, i.e: window.d.ts and place your extended definitions for the window object, as typescript global augmentation typically offers merging interfaces.
declare global {
interface Window {
myObject: YourObjectType;
}
}
// but make sure to export that as default so Typescript will consider it automatically on the project
export default global;

Override typescript module exports definition

I have installed plugin that ship with types definition.
declare module 'autobind-decorator' {
const autobind: ClassDecorator & MethodDecorator;
export default autobind;
}
But, I thought that type definition was wrong. I need to change to this
declare module 'autobind-decorator' {
const autobind: ClassDecorator & MethodDecorator;
export = autobind;
}
How can I do that?
How can I do that
Fork the project and publish (till the original gets fixed).
More
This is essentially if an author publishes a bad JS lib how do I fix it. You would fix it by forking. TypeScript doesn't offer much magic here.
Reason
If TypeScript offered a way to override it, it would lead to only confusion which definition is used.

Categories

Resources