Tag: JavaScript

  • JavaScript lacks it

    JavaScript lacks it

    I already hear screeches and screams from the crowd of people who praise JavaScript for being a bunch of human-readable text files.

    But yes, I feel that JavaScript lacks some “compiled” form of npm packages, with following properties:

    • single file per package, either in its library or executable form with strong distinction between the both.
    • Option to completely embed dependencies and deliver them inside of such file.
    • Make these files including embedded dependencies 100% preparsed and compiled into some “ready to link and execute” form, without need to spend on parsing, except may be some validation like “we can not assign to a const” or “we can not call a number, boolean, or other non-function stuff” or what generally speaking should be checked when e. g. a Class file (compiled Java file) loaded. But this would be something like “module” files, with references either from current package or fully qualified with package name
    scoped://namespace/library@1.2.5/path/to/module.js
    not-scoped://library@1.2.5/path/to/module.js
    this-package://modules/path/to/module.js

    But these strings are just readable references, internally it could be a list of per-compiled-file strings, with integer representations across compiled library or executable file.

    These references would be used to form binary” indications, like this:

    push bool true
    push fnref modules(155) exportedFunction
    call

    If we can represent this as kind of “arguments stack” machine.

    Or if we would represent this as classic stack frames, it would be like

    begin_call 1 # 1 argument expected
    load_arg_bool 0 true
    call fnref modules(155) exportedFunction

    Yes, this would be some sort of “dirty assembly” with most minimal set of instructions, as the nature of JS do not have too much ways to optimize that.

    Few instructions like that:

    • add operand_1_value operand_2_value receiver_ref – 2 arguments expected, do same as + operator, literally. Including String concatenation and auto-conversion
    • similar operations can be defined like substract, multiply, divide, pow and pow2 (with only 1 argument needed as second argument is already built-in. Also mod operation, and so on.
    • A set of unary operations can be plusify(+value) ,minusify (-value) or invert (!value) operations. Also increment and decrement deserves own commands.
    • alloc_local_vars n – allocate n local slots for variables of function, should be first instruction of method. n can be 0, if empty method or only receiver_ref of all such operations giving a result are discard_ref (see below).
    • labels_map label_expr1 label_expr2 label_expr3 would be second command of compiled function where list of position alias or position null for label without alias, where position is zero-based command index in the function body. If there is no labels in compiled function somehow, no_op command should be there.
    • set_local n value – set a local slot to value, where value can be a number, null, undefined, boolean, bigint, and so on, and value can be described in some const value_literal, local_ref n to reference other local value, arg n to reference arg of currently happening function call.
    • prop_get obj_value prop_name_value reciever_ref for reading member or indexer access, and so on where obj_value and prop_name_value are something that resolves together to receiver_ref = obj_value[prop_name_value] for property reading.
    • prop_set obj_value prop_name_value new_value is command to set the property like obj_value[prop_name_value] = new_value.
    • if condition_ref true_label false_label where condition_ref is something that can be used to get a value, like const value_literal , local_ref n or arg n and these true_label and false_label are created with special command.
    • label command marks this line by a name relevant to function scope, like label 1 some_alias or simply label 2 when it is not necessary to know or display in debugging the label name. The true_label and false_label are label_ref n expression or label_ref alias expression. Technically label is same as no_op command.
    • no_op well it just does nothing and can be used for empty code blocks, and at least no_op should be included into function body
    • the const_ref n would reference a constant, let it number or string, or other scalar value known at compile type, let it be property name, class name, a 0 in n > 0 expression or a string from template like "Hello ${name}".
    • undefined, null, true and false, as 0 and 1 are always known variants usable where expression should be, via const_ref null or const_ref true. Binary representations can have special bytecodes for such cases for const_ref true or const_ref false.
    • return value and return_void are return commands, where return_void is just more explicit analog of return const undefined. Returning from the topmost code of module would be an invalid operation.
    • call function_value receiver_ref would call a function, going into it, and return value would be assigned to value receiver, let it be local value, or object property reference prop_ref obj_value prop_name_value .
    • new_obj constructor_value receiver_ref would call a constructor with all that neuances and returned value would be assigned to receiver_ref which is same thing as for call command.
    • new_array length_value receiver_ref – allocates new array, usually with 0 length, what can be optimized with new_empty_array receiver_ref type of command
    • There is no such thing as ignored return value , technically it implemented by discard_ref what means a special value to be used for receiving of return values or results of other operations outcome of which never used.

    All this may not necessary be very detailed ultra-optimized instruction set, like 10-20 variants of add a b command, like add_int8 , add_uint32 or add_float64 with int8_to_float64 and other such conversion commands. This is not about replacing WebAssembly. It is about these two changes:

    • reduce size of node_modules
    • eliminate these post_install and pre_install scripts. The whole process of library installation into node_modules should consist of copying a library file into node_modules under special paths like @scope/library/compiled.jslibrary or library/compiled.jslibrary. This can include some check for semantic correctness, like do not use const undefined or const null as obj_value in prop_get or prop_set, or the label_ref should point to label declared in same function with label command, or never should use discard_ref as source of values.
    • Add support for libraries signing.
    • Allow to embed small, say less than 32 kb, (or optionally – all) dependencies into bullt library.
    • Speed up node applications startup
    • Allow to build single-file executables, or combination of launcher-file + main_app_library file.

    This would require changes at node_gcs, or at least custom loader for such libraries from modules, understanding these special imports.

    import {fancyFunction} from "scoped://libraries/@scope/library_name";
    import {fancyFunction as otherFancyFunction} from "non-scoped://libraries/library_name";

    For browser it would be something like

    import {fancyFunction} from "/assets/code_libraries/library.jslibrary";

    Or it would be

    <script type="js-library" src="/assets/code_libraries/library.jslibrary

    But this would require something like browser entry in the package.json and special markers in the header of library file.

    Whoa you read this far?

    Congratulations! You are at least a very-very patient person!

    What do you think about this idea of “binarized” JS libraries and executable files for JavaScript?

    I know there is WASM files and all that tooling with emscripten available, but this is not what I talking about, and it is impossible to point at WASM file as module or main or an NPM package, or load WASM file with <script src="/assets/scripts.wasm" type="webassembly" src_memory="/assets/default_mem.wmem" > , not speaking of access to window or worker context.

    PS: post illustration is photo of Java hardware processor made in Poland in 2011. Today I accidentally bumped into that example of computer archaeology looking for an illustration for this post