Using different Class Methods, depending on the used target compiler option - javascript

How can I tell TypeScript to use different-written methods (on the same class), depending on the used target option within the tsconfig.json file?
I'm currently re-writing one of my scripts into TypeScript to just "manage" one source, because at the moment I'm working with an ES5 and an ES6 file next to each other. Since TypeScript supports to just change the output target in the tsconfig.json file, I would just need one version to update and maintain.
The problem is I'm using a generator in the ES6 version, which theoretically shouldn't be this issue because TypeScript "adds" a pseudo generator to the top of my ES5 file. BUT: My Pseudo-Generator code, which I'm currently using on the ES5 file, is "cleaner" and way less code.
The Question
Is it possible to overwrite the respective "generator" method or using any special comment annotation (such as //#ts-target) to tell the compiler which code (function body) should be used depending on the used target in the configuration file? (Even If I couldn't found such solution on the official documentation).
An additional function or script, which can be added into the TypeScript compiler process would also help, I guess, because I'm compiling them using a small node.js script (which compiles both ES files without changing the tsconfig.json file directly.)
Or is there any kind of extension, to move different methods of the same class into different files? This way I could "extract" the respective "generator" methods, but this would cause another question: How can I link them depending on the target, because I'm using /// <reference /> linking on the main script file to get all together.
Any other idea?
class Option {
///#ts-target ES5
__walkerVariable1:any undefined
__walkerVariable2:any undefined
__walkerVariable3:any undefined
walker() {
/* Some Pseudo-Walker Code for ES5 */
}
///#ts-target ES6
*walker() {
/* Real Walker Code for ES6 */
}
}
Currently, a Pseudo-Generator code gets added in the ES5 version of my script. I want to prevent this by using a different method / function body with my own Pseudo-Generator. Therefore, I need to tell TypeScript that he should "ignore" the Pseudo-Generator in ES6 / the Real-Generator in ES5 and just render one of them depending on the used target option.

That was a bit tricky, but I found a solution thanks to this GitHub issue.
As mentioned above, I'm using my own node.js script to compile the TypeScript files into two different JavaScript versions (ES5 and ES6). Therefore, I'm using the TypeScript API with the ts.createProgram method. This method allows to add a host object as third parameter, which takes over some compiler processes and one of those is the file loader, called getSourceFile.
The rest is then relatively easy: Searching for a custom comment annotation (in my case \\\#ts-target:ES5 and \\\#ts-target:ES6 respectively) and filter them using RegExp. Maybe not the best solution, but it works!
function compileTS(){
let config = readConfig("ts/tsconfig.json");
let host = ts.createCompilerHost(config.options);
let sourceFile = host.getSourceFile;
// ES5 JavaScript
(function(config){
host.getSourceFile = function(filename) {
if(filename === "ts/options.ts"){
let file = fs.readFileSync("./ts/options.ts").toString();
file = file.replace(/[ ]+\/\/\/\#ts\-target\:ES6\s+([\s\S]*)\/\/\/\#ts\-target\:ES6/gm, "");
return ts.createSourceFile(filename, file, ts.ScriptTarget.ES5, true);
}
return sourceFile.call(host, filename);
}
let program = ts.createProgram(config.fileNames, config.options, host);
let emitResult = program.emit();
report(ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics));
if(emitResult.emitSkipped){
process.exit(1);
}
}(config));
// ES6 JavaScript
config.options.target = 2;
config.options.outFile = "../dist/js/tail.select-es6.js";
(function(config){
host.getSourceFile = function(filename) {
if(filename === "ts/options.ts"){
let file = fs.readFileSync("./ts/options.ts").toString();
file = file.replace(/[ ]+\/\/\/\#ts\-target\:ES5\s+([\s\S]*)\/\/\/\#ts\-target\:ES5/gm, "");
return ts.createSourceFile(filename, file, ts.ScriptTarget.ES2015, true);
}
return sourceFile.call(host, filename);
}
let program = ts.createProgram(config.fileNames, config.options, host);
let emitResult = program.emit();
report(ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics));
if(emitResult.emitSkipped){
process.exit(1);
}
}(config));
}

Related

Building a Javascript utility library with AMD compatibility

My goal is to build a set of javascript tools for functional programming, to be used by our company's web developers. I've tried giving a look at the Underscore annotated source but I'm new with RequireJS and AMD, so it's a lot confusing for me.
To start I just want to have a variable that gets available when my library is imported.
In this case booleans is a module that has functions returning boolean values. For example: _myLib.booleans.isDefined(var) - returns true is var is a defined variable.
No I have RequireJS setup, but how do I make a variable available for usage?
My main.js:
requirejs(['app/booleans'], function (booleans) {
var _myLib = {};
_myLib.booleans = booleans;
return _myLib;
});
Of course _myLib is undefined, I suppose it's because it is not assigned to any scope.
Can anyone give me some lights on building this library?
Thanks in advance.
If you want to produce a proper AMD library, you need to set it so that it calls define to define itself as an AMD module.
define(['app/booleans'], function (booleans) {
var _myLib = {};
_myLib.booleans = booleans;
return _myLib;
});
If you called your file myLib.js and provided a good configuration to RequireJS for it to find it, then, when you want to use it, you can do:
require(["myLib"], function (myLib) {
myLib.booleans.isDefined("moo");
});
Or in another module:
define(["myLib"], function (myLib) {
myLib.booleans.isDefined("blah");
});

Collect multiple RequireJS dependencies

There's a set of JS modules representing similar OOP Classes: think of e.g. different types of backend tasks (SendEmailTask, WriteToDbTask, WriteToDiskTask), or different actions on a drawing canvas (DrawArc, DrawLine, DrawBezier). Those are just examples.
Each of those is a single JS file, with its own define, and they're all located in a common directory. In a client module that depends on all of those, the dependency list and argument list have to include each one of those separately, e.g. something like:
define([
'tasks/sendEmailTask', 'tasks/writeToDbTask', 'tasks/writeToDiskTask', ...
], function (SendEmailTask, WriteToDbTask, WriteToDiskTask, ...) {
/* ... */
/* ... new SendEmailTask(); */
/* ... new WriteToDbTask(); */
/* ... new WriteToDiskTask(); */
});
and they both must be updated everytime a new module is added to the set, e.g. MakeCoffeeTask, which seems to me as a BadThing™.
Is there a way to avoid those last issues? I was thinking of a couple of possible ways, but I don't know how to make them work:
Create a kind of namespace module. Each of those sub-modules depends on the namespace one and adds its definition to it. But than if the client too depends only on the namespace, how do you ensure that client gets loaded after all sub-modules?
Express both dependencies and arguments with some kind of wildcard, like 'tasks/*' for dependency and < no idea > for arguments.
You can make a different module for each section. For example: 'tasks/main'
in the Task main you can include all the files you need. and in the task main, all the function of task will be assigned. and you call high level functions. Take look :
define(['tasks/main'], function(task){
task.add('Project Status meeting');
task.SendEmailNotification('tAll');
});
in the task/main.js :
define(['tasks/sendEmailTask', 'tasks/writeToDbTask', 'tasks/writeToDiskTask',], function(sendEmailTask, writeToDbTask, writeToDiskTask){
var task = {
add = function(taskObj){
// write your code using writeToDbTask, writeToDiskTask
},
SendEmailNotification = function(whom){
// write your code using sendEmailTask, writeToDbTask, writeToDiskTask
};
return task;
});
does it help you ?
I was not able to find any other solution. This is what I came up with in the meantime... it is just an improvement over current situation, but I don't think it solves the posed issues.
// in tasks/taskNamespace.js or in any client code
define (function(require) {
var _taskArray = [
require('tasks/sendEmailTask'),
require('tasks/writeToDbTask'),
require('tasks/writeToDiskTask'),
/* ... , */
];
var TaskNamespace = {};
_taskArray.forEach(function (taskClass) {
TaskNamespace[taskClass.name] = taskClass;
});
return TaskNamespace;
});
It takes advantage of RequireJS Sugar syntax and of JS Function.name coming new feature, proposed in ES6.
What's the end result? You still have to manually add each Class to a list, but it's only one list instead of two. WHOA

NodeJS - How to reference function in one require() file from another require() file?

This is my second weekend playing with Node, so this is a bit newbie.
I have a js file full of common utilities that provide stuff that JavaScript doesn't. Severely clipped, it looks like this:
module.exports = {
Round: function(num, dec) {
return Math.round(num * Math.pow(10,dec)) / Math.pow(10,dec);
}
};
Many other custom code modules - also included via require() statements - need to call the utility functions. They make calls like this:
module.exports = {
Init: function(pie) {
// does lots of other stuff, but now needs to round a number
// using the custom rounding fn provided in the common util code
console.log(util.Round(pie, 2)); // ReferenceError: util is not defined
}
};
The node.js file that is actually run is very simple (well, for this example). It just require()'s in the code and kicks off the custom code's Init() fn, like this:
var util = require("./utilities.js");
var customCode = require("./programCode.js");
customCode.Init(Math.PI);
Well, this doesn't work, I get a "ReferenceError: util is not defined" coming from the customCode. I know everything in each required file is "private" and this is why the error is occuring, but I also know that the variable holding the utility code object has GOT to be stored somewhere, perhaps hanging off of global?
I searched through global but didn't see any reference to utils in there. I was thinking of using something like global.utils.Round in the custom code.
So the question is, given that the utility code could be referred to as anything really (var u, util, or utility), how in heck can I organize this so that other code modules can see these utilities?
There are at least two ways to solve this:
If you need something from another module in a file, just require it. That's the easy one.
Provide something which actually builds the module for you. I will explain this in a second.
However, your current approach won't work as the node.js module system doesn't provide globals as you might expect them from other languages. Except for the things exported with module.exports you get nothing from the required module, and the required module doesn't know anything of the requiree's environment.
Just require it
To avoid the gap mentioned above, you need to require the other module beforehand:
// -- file: ./programCode.js
var util = require(...);
module.exports = {
Init: function(pie) {
console.log(util.Round(pie, 2));
}
};
requires are cached, so don't think too much about performance at this point.
Keep it flexible
In this case you don't directly export the contents of your module. Instead, you provide a constructor that will create the actual content. This enables you to give some additional arguments, for example another version of your utility library:
// -- file: ./programCode.js
module.exports = {
create: function(util){
return {
Init: function(pie) {
console.log(util.Round(pie, 2));
}
}
}
};
// --- other file
var util = require(...);
var myModule = require('./module').create(util);
As you can see this will create a new object when you call create. As such it will consume more memory as the first approach. Thus I recommend you to just require() things.

How to invoke arbitrary method from gradle?

Was trying to call out to the Saga Javascript code coverage from Gradle. After a long time playing with it, I was finally able to get it working. However, I am new to gradle and don't know if the way I did it makes the most sense or not! It seems like there might be quite a few ways to do this, so I thought I would post what I did here in the hopes that I might learn if this way was okay or if there was a better way.
After downloading the saga-core from maven central, it turns out that there is no Java "main". So I didn't think I could easily use JavaExec. It looked to me that I needed to create a Java object, set some params, and call the "run" method provided.
Here is the final build.gradle I ended up with:
apply plugin: 'groovy'
buildscript {
repositories {
mavenCentral()
}
dependencies {
// add the jar file withe the code you want to execute to gradle's classpath
classpath 'com.github.timurstrekalov:saga-core:1.1.2'
}
}
configurations {
compile
codeCoverageTask
}
dependencies {
groovy localGroovy()
codeCoverageTask 'com.github.timurstrekalov:saga-core:1.1.2'
}
// I thought this was simple and made sense to be co-located here rather than
// another file...
// getting the imports to compile requires adding the "buildscript" section above
import java.io.File
import com.github.timurstrekalov.saga.core.CoverageGenerator
class SagaCoverageTask extends DefaultTask {
def outputDirectory
def baseDirectory
def includes = 'my_includesPattern_here'
def excludes = 'my_excludesPattern_here'
def noInstrumentPatterns = [ 'my_noIntrumentPatterns_here' ]
#TaskAction
def runCoverage() {
// check these were set correctly!
println outputDirectory
println 'baseDir' + baseDirectory
// create an instance of the object
CoverageGenerator generator = new CoverageGenerator(baseDirectory, includes, excludes, outputDirectory)
generator.noInstrumentPatterns = noInstrumentPatterns
// there are params, but they would be handled in the same way if needed
generator.run() // invoke the arbitrary method
}
}
task genCodeCoverage(type: SagaCoverageTask) {
// needed the values of task properties, so these are set here
outputDirectory = new File('' + reportsDir + '/coverage')
baseDirectory = new File('' + projectDir + '/src')
}
Looks like a decent start. File objects should generally be created with the project.file() method because it handles relative paths better than new File() (although it's not a problem in this particular case). For example: file("$reportsDir/coverage").
If the task class grows bigger or you want to reuse it across projects/builds, you should move it somewhere else (e.g. into buildSrc) and add some test coverage.
By annotating the task class properties with #InputDirectory, #OutputDirectory, etc. you can make it so that the task will only run if some input has changed or previously generated output isn't available anymore. This can speed up the build.

node.js and browser code reuse: importing constants into modules

I have some constants in JavaScript that I'd like to reuse in several files while saving typing, reducing bugs from mistyping, keeping runtime performance high, and being useful on either the node.js server scripts or on the client web browser scripts.
example:
const cAPPLE = 17;
const cPEAR = 23;
const cGRAPE = 38;
...(some later js file)...
for...if (deliciousness[i][cAPPLE] > 45) ...
Here are some things I could do:
copy/paste const list to top of each file where used. Oh, Yuck. I'd rather not. This is compatible with keeping the constant names short and simple. It violates DRY and invites all sorts of awful bugs if anything in the list changes.
constant list ---> const.js
on browser, this is FINE ... script gets fed in by the html file and works fine.
but on node.js, the require mechanism changes the constant names, interfering with code reuse and requiring more typing, because of how require works....
AFAIK This doesn't work, by design, in node.js, for any const.js without using globals:
require('./const.js');
for...if...deliciousness[i][cAPPLE] > 45 ...;
This is the node.js way:
(... const.js ....)
exports.APPLE = 17;
(... dependency.js ... )
var C = require('./const.js');
for...if...deliciousness[i][C.APPLE] > 45.....
so I would either have to have two files of constants, one for the node.js requires and one for the browser, or I have to go with something further down the list...
3 make the constants properties of an object to be imported ... still needs two files... since the node.js way of importing doesn't match the browser. Also makes the names longer and probably takes a little more time to do the lookups which as I've hinted may occur in loops.
4 External constant list, internal adapter.... read the external constants, however stored, into internal structure in each file instead of trying to use the external list directly
const.js
exports.cAPPLE = 17
browser.js
const cAPPLE = exports.cAPPLE;
...code requiring cAPPLE...
node.js
CONST = require(./const.js)
const cAPPLE = CONST.cAPPLE;
...code requiring cAPPLE...
This requires a one-time-hit per file to write the code to extract the constants back out, and so would duplicate a bunch of code over and over in a slightly more evolved cut and paste.
It does allows the code requiring cAPPLE to continue to work based on use of short named constants
Are there any other solutions, perhaps a more experienced JavaScripter might know, that I might be overlooking?
module.exports = Object.create({},{
"foo": { value:"bar", writable:false, enumerable:true }
});
Properties are not writable. Works in strict mode unlike "const".
I would just make them global keys:
...(module consts.js)...
global.APPLE = 17;
global.PEAR = 23;
global.GRAPE = 38;
...(some later js file)...
var C = require('./const.js');
for (var i = 0; i < something.length; i++) {
if (deliciousness[i][global.APPLE] > 45) { blah(); }
}
They wouldn't be enforced constants, but if you stick to the ALL_CAPS naming convention for constants it should be apparent that they shouldn't be altered. And you should be able to reuse the same file for the browser if you include it and use it like so:
var global = {};
<script src="const.js"></script>
<script>
if (someVar > global.GRAPE) { doStuff(); }
</script>
You can make an object unwritable using Object.freeze .
var configs={
ENVIRONMENT:"development",
BUILDPATH:"./buildFiles/",
}
Object.freeze(configs);
module.exports=configs;
Than you can use it as constant
var config=require('config');
// config.BUILDPATH will act as constant and will be not writable.

Categories