How to expose a class to the global scope with esbuild? - javascript

Update
user #TKoL suggested defining a property in the window object. This produces the result I wanted to achieve, although I do not know if it is the correct way to proceed. I will be using this method for the time being. Thanks!
I am using esbuild in my project (first time using a bundler / developing JS in this "style").
The project is a small web component for which I developed a class that the end user should be able to utilize in their scripts.
// this is the component, which is then bundled and minified by esbuild.
// i have omitted various imports required in the class here.
export default class Widget extends HTMLElement {
/// code here is not relevant
}
customElements.define('custom-widget', Widget);
As it stands, the end user is able to utilize the widget from HTML like this:
<custom-widget some-custom-attribute="some-value"></custom-widget>
let widget = document.querySelector('custom-widget');
widget.someMethodDefinedInTheClass();
However, I would like to allow the user to do it all via JavaScript as well.
How can I make esbuild expose the Widget class to the global scope? I'd like to do this to enable behaviours such as:
let wOptions = {}; // object of initialization options for the widget
let widget = new Widget(wOptions);
someContainer.append(widget);
widget.someMethodDefinedInTheClass();

To make any way bundled js file to expose anything to the global scope you have to set it as a property of global object (that represents the global scope) like
globalThis.Widget = Widget;
globalThis keyword is a best way to use global object in javascript because it works the same on browser page, nodejs or service worker environment. If your code will be only executed on browser page you can also use window keyword.

Related

How to require plugins once and access it globally?

My app was made before the Nativescript CLI v5.x and got to a point where I needed to update CLI versions. The CLI version 5.4.2 suggests to add "tns-core-modules/" for every 'require' I have in my app since short import is deprecated in v5.2.0, since I have tons of plugins required on tons of pages, it's quite a pain in the ass changing every require line. How can I require all my plugins once and then use them on every page or anywhere I need it?
In javascript you have access to an object that always exists in the global scope, this global object provides variables and functions, the goal is to add your library to it.
You have the window object for example in a browser.
Find the global object (look post here) on your project and add your library to this object like :
var myLib = require('myLib');
var globalObject = Function('return this')();
globalObject.myLib = myLib;

Plain Javascript as Angular 2 service

I need to add a hosted third-party JavaScript file in an Angular 2 component. This file is updated anytime a change is made in the associated third-party vendors proprietary system, so I cannot simply pull down a copy, include it locally, and import it into the project.
Typically I would include this file at a top level in a script tag, and then simply use declare <windowvar>: any to get access to it. However in this case, since the component itself is trying to load the script, I cannot declare the window variable because it does not exist on the window object at the time the component is loaded, which generates an error.
I can load the script by manually adding a script tag, and that works, however I need access to the window variable it creates in order to use it properly. And I cannot simply use an interval to look for it because typescript throws a fit that <windowvariable> does not exist on object window.
Is there any way I can 1) Load the hosted JavaScript file inside the component, and 2) Get access to the window variable created by the loaded JavaScript file?
Update 1: Based on the comments, the previous solution will be not help you.
You can do that by using OpaqueToken in Angular2
1. Create a Token that is used to find an instance as below in a separate ts file.
import { OpaqueToken } from '#angular/core'
export let name_of_The_Token = new OpaqueToken('name_Of_The_Window_Object');
2. In your App.module, you need to import and declare a variable that is the name of your window object which makes the Token as a angular2 service so that you can use properties, methods in that javascript file across your components.
import { name_of_The_Token } from '/* file_Path */';
declare let name_Of_The_Window_Object : any; //below your import statements
Step 3: Inject it to providers array of your module.
{ provide : name_of_The_Token , useValue : name_Of_The_Window_Object }
Guidance to use this token in components
Import the token just like any other service and #Inject from angular-core
import { name_of_The_Token } from '/* file_Path */';
import { Inject } from '#angular/core';
In constructor of the component
constructor(#Inject( name_of_The_Token ) private _serviceObject : any )
Any where in your component you can use the variables and methods of your javascript file as
this._serviceObject.method1()
this._serviceObject.variable1
.....
Note: One drawback is that you will not get intellisense.
Overcoming it:
If you are looking for intellisense you need to wrap the methods and variables inside an interface and use it in the type**(instead of any)** of your token as
export interface myCustom {
method1(args): return_Type;
method2(args): void;
.....
}
LIVE DEMO of ToasterService
Effectively, you're trying to pick up a "global module" javascript library from a CDN. (I assume the 3rd-party lib is not in CommonJS, AMD, UMD, or other module format, since it is accessed through a sole global variable.)
So the first question is where is the corresponding .d.ts file? It contains the names and interfaces that inform Typescript of the 'shape' of the library, as well as declaring that global variable will exist. If your 3rd-party doesn't provide it you'll need to write it yourself. It will contain not just the declaration of the global var, like
declare var theGlobalVarInQuestion: IInterfaceOfStuffInsideLibrary;
but also the declaration of said Interface, and its properties and their types, all the way down. Like this: https://github.com/catamphetamine/libphonenumber-js/blob/master/index.d.ts
You can include the .d.ts file in /node_modules/#types/nameOfSaidLibrary but you'd need to check it into your source repo (with possible .gitignore gymnastics) especially because a npm prune will remove it. Or if you put it elsewhere, modify the tsconfig.json typeroots property to look in both that place in addition to its usual /node_modules/#types/ folder.
Just to be clear, the .d.ts file doesn't (and shouldn't) actually create the variable; it just states that it will be there so the Typescript compiler won't complain. Whether or not it is there at runtime is decided by however you're loading the js.
If you're not loading it via script tag in the index.html, then either a Typescript import statement in the consuming component can do so (given the right config of SystemJS or whatever you're using) or the consuming component can dynamically create and append a new script tag to the index.html. Just make sure your module loader isn't trying to download and immediately bundle it with the rest of your app at buildtime. It sounds like you want the lib to be downloaded anew each time at runtime.

What is javascript "global" variable?

I am trying to get my app that uses BreezeJs to work with Webpack/commonjs.
My app is in typescript that compiles down to commonjs javascript.
There is a line in the breeze code that looks like this:
function __requireLibCore(libName) {
var window = global.window;
if (!window) return; // Must run in a browser. Todo: add commonjs support
The problem is that global is just an empty object ({}). It does not have window in it.
I can use webpack to make global equal to something, but I don't know what this global object is. (Try googling for "javascript global")
Is it a generic typescript construct that somehow is not present in commonjs?
Or something specific to breeze?

Creating a "controller" script for Node.js

I'm creating a program in Node.js. I'm pretty new to programming anything other than small javascript functions for websites so please bear with me if my terminology/ideas are totally wrong.
I originally had the entire program in one giant (~500 line) script file. Several people suggested I split it up into separate classes, where each class only has one 'job' to complete. I like this idea as it has helped me really streamline my code and make it more modular and manageable.
My issue is: How do I access these classes from a central file?
For instance, pretend I have 3 classes, in 3 separate javascript files, all containing 3 functions each. I want to access and pass data to/from all of these from one central "controller" script. What's the best way to do this? Can I just require it into a variable, then access the script's functions like so?
var class1 = require('./class1.js');
class1.function1(); // call the first function contained in class1.js
Is such a thing even possible? Again, totally new to this.
NodeJS supports CommonJS modules. A CommonJS module provides three global variables: module, exports and require.
You can export your API by adding to the exports object and require these files just like other node modules (add ./ to indicate that it is relative to the current file), assign it to a variable and access the values you added to that files exports object:
// first-module.js
exports.hello = 'world';
exports.num = 23;
// main.js
var first = require('./first-module');
console.log(first.hello);
console.log(first.num);
You need to add functions to the exports object in class1.js.
require("./class1") will return this object.

How to correctly modularize a node.js project?

I'm creating a node.js project following the class constructor pattern:
function my_class(x,y){
this.x = x;
this.y = y;
}
The starting point of the project is the main.js file. Any class of the project must be able to access global objects (such as "world" and "socket") which are defined on main.js. I found 4 options:
I define my classes inside main.js. They'll have access to main.js's globals for being on it's closure, but main.js will become bloated.
I move the class to another file such as my_class.js and require() it. This doesn't work because my_class's instances will lose the closure context and no longer be able to access main.js's globals.
I move the class to another file and manually inject dependencies to it's constructor (ex: my_class(world,socket)). The problem is, code becomes much more complicated and weird semantics such as "my_instance.world" pop on the source, which is nonsense because "world" is not property of my_class.
I move the class to another file and require it using my_class = eval(fs.readFileSync(()) instead of require. This works just fine as my_class gets main.js's closure context, and is the solution I'm using, but seems hacky.
Which is the right way to modularize such node.js project?
If I understood you correctly the possible solution:
main.js:
(function(){
var global = "test"; // this you wanna have as a closure
var my_class = require('./my_class')(global);
my_class.go();
})();
my_class.js:
module.exports = function(global){
return {
go: function(){
console.log(global);
}
};
};
So it's similar to your 3. option
Your problem seems tricky because you have a circular dependency: main.js depends on the functionality of my_class and my_class depends on the data of main.js.
By putting the data of main.js into the global object you resolve the circular dependency:
main.js depends on the functionality of my_class.js
main.js depends on the data in the global object
my_class.js depends on the data in the global object
Now, to get rid of putting the data into the global object, implement a third module in let us say data.js. Then you require the sources like this:
main.js requires data.js
main.js requires my_class.js
my_class.js requires data.js
Since modules in node.js are singletons both main.js and my_class.js will get the same instance of data.js.
If you want to make variables in main.js available anywhere, then you can assign properties to the global object. See node.js global variables? for example. It would work fine as long as you don't over do it. With Neo's solution, you gain a little more flexibility for example with testing, because you can "inject" an arbitrary object into the module. Not every module has to use the same "global" per se.

Categories

Resources