I have a class that has roughly this structure:
function MyClass() {
// constructur stuff
}
MyClass.prototype.myFunc = function () {
// example function
};
MyClass.myStaticFunc = function () {
// example static function
};
I spent some time now setting up the closure compiler annotations and finally got rid of all warnings. And what do you know, it reduces the size by a spectacular 100%. So then I read about exporting functions, but window['MyClass'] = MyClass will only export the constructor. To be honest, I'd rather not export every single method individually. I thought the compiler would export and not obfuscate all publicly available methods but those with a #private annotation.
What's the best way to teach the closure compiler to do that and not have to export every method individually?
Using ADVANCED_OPTIMIZATIONS you must export EVERY public method and property. If you do not want the public methods and properties renamed, then use SIMPLE_OPTIMIZATIONS.
See my Which Compilation Level is Right for Me post for more details.
I believe I found the answer: I can annotate methods with #export and run the compiler with --generate_exports. But maybe someone has an even better way.
Related
I am gradually improving a codebase that originally had some AngularJs in various versions and some code that was not in a framework at all using various versions of a software API. (For some reason this API is available - to pages loaded through the application - on AngularJS's $window.external...go figure.)
In my pre-ES6, AngularJs 1.8 phase, I have three services that interact with the software's API (call them someAPIget, someAPIset, and someAPIforms). Something like this:
// someAPIget.service.js
;(function () {
var APIget = function ($window, helperfunctions) {
function someFunc (param) {
// Do something with $window.external.someExternalFunc
return doSomethingWith(param)
}
return {
someFunc: someFunc
}
}
angular.module('someAPIModule').factory('someAPIget', ['$window', 'helperfunctions', someAPIget])
})()
I then had a service and module a level up from this, with someAPIModule as a dependency, that aggregated these functions and passed them through under one name, like this:
// apiinterface.service.js
;(function () {
// Change these lines to switch which API service all functions will use.
var APIget = 'someAPIget'
var APIset = 'someAPIset'
var APIforms = 'someAPIforms'
var APIInterface = function (APIget, APIset, APIforms) {
return {
someFunc: APIget.someFunc,
someSettingFunc: APIset.someSettingFunc,
someFormLoadingFunc: APIforms.someFormLoadingFunc
}
}
angular.module('APIInterface').factory('APIInterface', [APIget, APIset, APIforms, APIInterface])
})()
I would then call these functions in various other controllers and services by using APIInterface.someFunc(etc). It worked fine, and if we switch to a different software provider, we can use our same pages without rewriting everything, just the interface logic.
However, I'm trying to upgrade to Typescript and ES6 so I can use import and export and build some logic accessible via command line, plus prepare for upgrading to Angular 11 or whatever the latest version is when I'm ready to do it. So I rebuilt someAPIget to a class:
// someAPIget.service.ts
export class someAPIget {
private readonly $window
private readonly helperfunctions
static $inject = ['$window', 'helperfunctions']
constructor ($window, helperfunctions) {
this.$window = $window
this.helperfunctions = helperfunctions
}
someFunc (param) {
// Do something with this.$window.external.someExternalFunc
return doSomethingWith(param)
}
}
}
angular
.module('someAPImodule')
.service('someAPIget', ['$window', 'helperfunctions', someAPIget])
Initially it seemed like it worked (my tests still pass, or at least after a bit of cleanup in the Typescript compilation department they do), but then when I load it into the live app... this.$window is not defined. If, however, I use a direct dependency and call someAPIget.someFunc(param) instead of through APIInterface.someFunc(param) it works fine (but I really don't want to rewrite thousands of lines of code using APIInterface for the calls, plus it will moot the whole point of wrapping it in an interface to begin with). I've tried making APIInterface into a class and assigning getters for every function that return the imported function, but $window still isn't defined. Using console.log statements I can see that this.$window is defined inside someFunc itself, and it's defined inside the getter in APIInterface, but from what I can tell when I try to call it using APIInterface it's calling it without first running the constructor on someAPIget, even if I make sure to use $onInit() for the relevant calls.
I feel like I am missing something simple here. Is there some way to properly aggregate and rename these functions to use throughout my program? How do alias them correctly to a post-constructed version?
Edit to add: I have tried with someAPIget as both a factory and a service, and APIInterface as both a factory and a service, and by calling APIInterface in the .run() of the overall app.module.ts file, none of which works. (The last one just changes the location of the undefined error.)
Edit again: I have also tried using static for such a case, which is somewhat obviously wrong, but then at least I get the helpful error highlight in VSCode of Property 'someProp' is used before its initialization.ts(2729).
How exactly are you supposed to use a property that is assigned in the constructor? How can I force AngularJS to execute the constructor before attempting to access the class's members?
I am not at all convinced that I found an optimal or "correct" solution, but I did find one that works, which I'll share here in case it helps anyone else.
I ended up calling each imported function in a class method of the same name on the APIInterface class, something like this:
// apiinterface.service.ts
// Change these lines to switch which API service all functions will use.
const APIget = 'someAPIget'
const APIset = 'someAPIset'
const APIforms = 'someAPIforms'
export class APIInterface {
private readonly APIget
private readonly APIset
private readonly APIforms
constructor (APIget, APIset, APIforms) {
this.APIget = APIget
this.APIset = APIset
this.APIforms = APIforms
}
someFunc(param: string): string {
return this.APIget.someFunc(param)
}
someSettingFunc(param: string): string {
return this.APIset.someSettingFunc(param)
}
someFormLoadingFunc(param: string): string {
return this.APIforms.someFormLoadingFunc(param)
}
}
angular
.module('APIInterface')
.factory('APIInterface', [APIget, APIset, APIforms, APIInterface])
It feels hacky to me, but it does work.
Later Update:
I am now using Angular12, not AngularJS, so some details may be a bit different. Lately I have been looking at using the public-api.ts file that Angular12 generates to accomplish the same thing (ie, export { someAPIget as APIget } from './filename' but have not yet experimented with this, since it would still require either consolidating my functions somehow or rewriting the code that consumes them to use one of three possible solutions. It would be nice not to have to duplicate function signatures and doc strings however. It's still a question I'm trying to answer more effectively, I will update again if I find something that really works.
Is is possible to have one class and distribute it's methods over multiple files?
My class is now in one file and it's getting to be a long file, that is becoming hard to navigate developing.
I thought of extending a class, but that is building on top of a class. And multiple extended classes don't communicate amongst each other, right?
So the main file would have the constructor and the other files would have one or just a few methods of its class.
You can assign to properties of the prototype, just like in the old days when functions were used instead of classes - import the functions from the other files, and after the class is defined, assign the properties:
// main.js
import method1 from './method1';
import method2 from './method2';
// or use require() syntax, depending on how your environment is set up
class Foo {
constructor(name) {
this.name = name;
// ...
}
}
Foo.prototype.method1 = method1;
Foo.prototype.method2 = method2;
// method1.js
export default function() {
console.log('method1 running for ' + this.name);
};
Both method1 and method2 will still be able to reference this to refer to the instance, just like they were defined as a method directly inside Foo.
You can use import syntax with ES6 modules (eg <script type="module") or in a module bundler system like Webpack. In node, import using require(path) and export by assigning to module.exports or properties of module.exports.
If a single class is getting so big that you're worried the file containing it is getting too big, I'd say the best thing you can do is refactor the class into smaller classes that are more focussed on solving a single problem.
If you can't do that, then you can have the main class's file create the class:
class Example {
}
...and then have multiple files that add methods to its prototype:
Example.prototype.doThis = function doThis() {
// ...
};
Example.prototype.doThat = function doThat() {
// ...
};
You lose the ability to use super in those methods, but other than that it works.
I thought of extending a class, but that is building on top of a class. And multiple extended classes don't communicate amongst each other, right?
I'm not sure what you mean about not being able to communicate between superclasses and subclasses. The instance data is common to all, and they can use each other's methods.
I'm getting rather confused as to if something is possible or not.
I create a module that contains the following:
export function logText(){
console.log('some text');
}
export class Example {
constructor(){
logText();
}
}
The intent is for the user to call new Example to start off the module logic.
import { logText, Example } from 'example';
// Do some magic here to modify the functionality of logText
new Example();
Is it possible for the end user to modify logText?
It would make sense for there to be a way for users to do something like this, or they'd have to take the entire module into their own repo just to make small functionality tweaks.
I frequently see repos with lots of functions exported that are useless without the users having to remake almost all the functionality manually, making it rather pointless to do. One good example is this repo whre theuy even call the exported functions their 'API'. In that example these are rather pointless exports and at worse would just cause issues if someone tried to use them in conjunction with the main function. But if you could modify them and have them still run then it would make sense to me.
Given this:
import { logText, Example } from 'example';
Is it possible for the end user to modify logText?
Since you aren't being very specific about what you mean by "modify logText", I'll go through several options:
Can you reassign some other function to the variable logText?
No. You cannot do that. When you use import, it creates a variable that is const and cannot be assigned to. Even if it wasn't const, it's just a local symbol that wouldn't affect the other module's use of its logText() anyway. The import mechanism is designed this way on purpose. A loader of your module is not supposed to be able to replace internal implementation pieces of the module that weren't specifically designed to be replaced.
Can you modify the code inside of the logText function from outside of the module that contains it?
No, you cannot. The code within modules lives inside it's own function scope which gives it privacy. You cannot modify code within that module from outside the module.
Can you replace the logText() function inside the module such that the implementation of Example inside that class will use your logText() function?
No, you cannot do that from outside the module. You would have to actually modify the module's code itself or someone would have to design the Example interface to have a replaceable or modifiable logText() function that the Example object used.
For example, logText() could be made a method on Example and then you could override it with your own implementation which would cause Example's implementation to use your override.
Code in the module that you do not modify:
export class Example {
constructor(){
this.logText();
}
logText() {
console.log('some text');
}
}
Code doing the import:
import { Example } from 'example';
class MyExample extends Example {
constructor() {
super();
}
logText() {
console.log("my own text");
}
}
let o = new MyExample();
Can you create your own version of logText and use it locally?
Sure, you can do that.
function myLogText() {
do your own thing
}
And, you could even NOT import logText so that you could use the symbol name logText() locally if you wanted. But, that won't affect what Example does at all.
Are there ways to design the example module so that that logText() can be easily replaced.
Yes, there are lots of ways to do that. I showed one above that makes logText a method that can be overriden. It could also be passed as an optional argument to the Example constructor.
There could even be an exported object that allowed the caller to replace properties on that object. For example:
export const api = {
logText: function logText(){
console.log('some text');
}
};
export class Example {
constructor(){
api.logText();
}
}
Then, use it like this:
import { api, Example } from 'example';
api.logText = function() {
console.log('my Text');
};
I would generally not recommend this because it sets you up for usage conflicts between multiple users of the same module where each one tries to modify it globally in ways that conflict with each other. The subclassing model (mentioned above) lets each use of the module customize in its own way without conflicting with other usages of the module.
Is it possible for the end user to modify logText?
No, that's not possible, import bindings are immutable, and function objects are basically immutable wrt the code they contain.
It would make sense for there to be a way for users to do something like this, or they'd have to take the entire module into their own repo just to make small functionality tweaks.
Why not make the log function an optional argument in the constructor? Usually when something is variable it becomes a parameter.
export class Example {
constructor(log=logText){
log();
}
}
What is the best practice way to define static class private properties in JavaScript ES6 in Node.js?
I have the following Log class:
'use strict';
const moment = require('moment');
const LOG_DATE_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ';
module.exports = class {
static debug(txt) {
console.log(`${moment().utc().format(LOG_DATE_FORMAT)} [debug] ${txt}`);
}
static info(txt) {
console.info(`${moment().utc().format(LOG_DATE_FORMAT)} [info] ${txt}`);
}
static warn(txt) {
console.warn(`${moment().utc().format(LOG_DATE_FORMAT)} [warn] ${txt}`);
}
static error(txt) {
console.error(`${moment().utc().format(LOG_DATE_FORMAT)} [error] ${txt}`);
}
};
Is defining moment and DATE_FORMAT outside of the module.exports and thus class definition the correct way of doing this?
Yes, it's an acceptable way of doing things. Since you want to define some variables that you share among all your static methods, you have basically four choices:
Define them as statics on the class also so you can reference them using the class name as a prefix. This has the benefit or drawback (depending upon what you want) that it makes them publicly accessible on your exported class.
Define them as module level variables as you've done. This makes them accessible by all the static methods, but keeps their use private to your module.
Redefine them separately inside of each method.
Define them as globals.
Option #3 seems like a lot of repetition and certainly wouldn't be very DRY.
Option #4 does not seem like a good way to go since there's no reason to make these globals (you pretty much never want to use globals with node.js anyway).
So, it comes down to option #1 or #2 and which to choose totally depends upon whether you want their values to be accessible outside the module or not. If you want them exported also, then make them static properties on your class and refer to them with the class prefix. If you only want to use them inside the module, then what you have done is perfect.
I've recently written a huge library in ECMAScript2015 which I would like to write a type definition for, so that it can be used in TypeScript projects.
Let's call the lib 'myShop' for now, with the basic structure being:
myShop.init(params): // function
myShop.cart.goto(); // function in namespace 'cart'
myShop.info; // variable that contains trivial information
My question is what the best practices would be, for typing the library. For just the sake of getting autocomplete in IntelliJ, there are many ways that seem to work.
My approach so far looks like this:
declare module myShop {
function init(param:List<string>): Promise;
var cart: ICart;
var info: Object;
}
interface ICart {
goto(): void;
}
Any tips for improvement? Is an interface for 'cart' correct? Should it rather be a module?
Thank you in advance!