This has been a long time coming, but since it requires no more than a day’s work under our new codebase, I figured I could add it as a surprise. QTX now supports a library project type!
What do you mean, libraries?
While the IDE focuses on visual client projects (browser) and non-visual server projects (node|deno), some of our backers have voiced that it would be really useful to have a more 1:1 approach to Javascript.
Typically these backers work with JavaScript daily, either maintaining existing websites, or having to write JS for new projects. While we can all agree that JS is indeed powerful in the right hands — it is not very productive. Just from a time perspective (the time it takes you to implement something) Object Pascal is without a doubt more productive.
For them, being able to write code that doesnt pull in the whole might of the QTX runtime library is a productivity boost.
How does a library look like?
If you have ever written a DLL library with Delphi or Freepascal, you already know what a library unit looks like. It has the same structure as a program unit except that it starts with the “library” keyword. Here is a clean cut library example which contains a single function:
Since QTX contains a full compiler you need to make sure you dont apply symbol obfuscation in the project options, or the names of your functions and procedures will be garbled. But when we compile the above we get the following code:
In other words, a library emits a javascript file that you can work with in JavaScript projects, and that other JavaScript developers can make use of. And just like a native DLL you need to make sure the functions and procedure names are valid. I suggest you prefix them with a unique combo (just like we use QTX as a class prefix in the RTL) to avoid confusion or namespace poisioning.
Following the rules
A lot has happened in the world of JavaScript these past 10 years, so you might want to post-process the library code a bit before you shart sharing it. I mentioned namespace poisioning in the above paragraph, and that is very important to avoid. Basically that means, that if you simply load in the generated javascript file – you risk overlapping with existing Javascript functions with the same name.
It is also slightly bad taste to define your functions in global scope, you probably want to isolate your code inside an enclosure, like this:
var _myLibrary = (
function setup() {
// Library code here
})();
What Javascript developers typically do, is that they return an object (also called a namespace object) in the above function, which only exposes the functions you decide as named members. So something like this:
var __mylibrary = (
function Setup() {
var _tmp = {};
function hello() {
return "hello";
}
_tmp["hello"] = hello;
return _tmp;
}
)();
That probably looks very complicated, but it’s really not! Let’s break it down and have a closer look.
- The ( and ) characters that we wrap around the “function Setup()” basically tucks away the content as an expression. This means that it’s content is not visible in the global space (which is what we want). The “();” at the end means we execute the expression block. This works because the expression block will resolve to a function.
- If you look closer at the actual function, you see that we create an empty object at the beginning of Setup(), and that this object is later returned as the result.
- Since this is all wrapped as an expression, that object ultimately bubbles out of the function, and further – out of the expression. This is more or less the same as if you returned an object instance from a Delphi function. The only difference here is that we sculpt that object in it’s entirety inside the function.
- Once the object bubbles out of the expression, we store it in the _mylibrary variable. This means that we can only call whatever functions was registered in that object, everything else that those functions might depend on — is neatly isolated inside the expression block and not reachable from public scope.
So if we want to reach the “hello” function in the example code above, we have to call it via the _mylibrary object, like this:
console.log( __mylibrary.hello() );
The moment you null out the __mylibrary variable — the entire library vanishes into the void of the garbage collector.
Creating the namespace object from pascal
You can create that this namespace-object as a part of the pascal startup code if you like (simpler). You still need a bit of post processing (wrapping it as an expression), but it does help simplify things. You can do this for example:
Since there are only functions in JavaScript we can safely add a inline-assembly return call at the end without causing problems. In this case we just dump out the object we just populated, exactly like i outlined earlier in JS code.
In this case you would just remove the whole “var main = function
” part (and curley brackets), since you are already putting the whole shabam inside the Setup() function. As a bonus — with such a namespace object you can use obfuscation. The compiler will automatically map the obfuscated symbols. So “CoolPublicFunction” will still be called that in the returning object — even though it might be called something else entirely inside the expression block.
An alternative way which doesnt involve creating an empty JS object, is to use anonymous classes. Anonymous classes makes it a lot easier when you suddenly want to add some structures to the object, like version info or something more elaborate. Remember that these classes are not pascal classes, they are “in place sculpted JS objects”:
The output of using an anonymous class is:
var main = function() {
NameSpace = {
"CoolPublicFunction" : CoolPublicFunction
};
return NameSpace;
}
All things considered, libraries via QTX represents a significant time saver. Having to add maybe 5-6 lines in post processing – is nothing compared to the amount of time you save by using pascal.
Creating a global object
JavaScript developers have for the most part moved away from putting everything and it’s grandmother in global scope, but between you and me – in 99% of the cases it makes no difference. Stock JS libraries like jQuery registers as a global object, as does more or less all the major libraries.
I am not advocating that you stuff everything in global space (e.g application critical data). But for libraries that are meant to be used by your whole website the criteria is different.
Making the library globally available simply means registering it with the window object, like this:
window["mylib"] = __mylibrary;
Once registered there, you can access it from anywhere as “mylib”. As you probably understand, with the sheer number of libraries out there this can potentially lead to overlaps and conflicts. I cannot stress enough how important it is to prefix your names with a combination that uniquely identifies you particular code.
Cherrypicking from the RTL
The moment you start referencing the RTL in your library code, the complexity of the generated output increses. Use of TObject will bring in VMT typing, sprintf and a lot more. Fundamental units such as qtx.sysutils.pas depends heavily on lookup tables, so immediately you see a bump in the footprint. Both in initialization code and the included methods.
Having said that, it is still mince-meat compared to what other compilers are spitting out. The QTX RTL is optimized for speed and some of the functions in the RTL are actually libraries in their own right in the JS world. The memory buffer management, streams and datatype conversion (moving between untyped memory and typed datatypes) that we take for granted in QTX, simply do not exist in the JavaScript reality. This is why QTX can do stuff out of the box that would take JS developers days and weeks to accomplish.
As always is the case with object pascal, the RTL usually adds some initial size to your output — but once added, the rest of the code that uses the RTL is typically very small and fast. This is philosophical debate more than it is technical. Object Pascal is a language of practicality. We favor safety and completeness more than we do codesize. I agree that the code elimination process could be better and more agressive, but it ultimately boils down to inter-dependencies in the RTL (of functions using existing functions, thus both have to be included in the output).
Either way, you can cherry pick all you want from the RTL, and compared to what vanilla JS developers have to work with – you can ramp up functionality quickly and efficiently.
In the future we might put more work into doing some of the registrations for you, like wrapping the code as an expression, using attributes to mark functions for export and so on — but right now, low level is what people have asked for. So that is what we provide.