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. IncludingStringconcatenation and auto-conversion- similar operations can be defined like
substract,multiply,divide,powandpow2(with only 1 argument needed as second argument is already built-in. Alsomodoperation, and so on. - A set of unary operations can be
plusify(+value) ,minusify(-value) orinvert(!value) operations. Alsoincrementanddecrementdeserves own commands. alloc_local_vars n– allocatenlocal slots for variables of function, should be first instruction of method.ncan be 0, if empty method or onlyreceiver_refof all such operations giving a result arediscard_ref(see below).labels_map label_expr1 label_expr2 label_expr3would be second command of compiled function where list ofposition aliasorposition nullfor label without alias, where position is zero-based command index in the function body. If there is no labels in compiled function somehow,no_opcommand 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 someconst value_literal,local_ref nto reference other local value,arg nto reference arg of currently happening function call.prop_get obj_value prop_name_value reciever_reffor reading member or indexer access, and so on whereobj_valueandprop_name_valueare something that resolves together toreceiver_ref = obj_value[prop_name_value]for property reading.prop_set obj_value prop_name_value new_valueis command to set the property likeobj_value[prop_name_value] = new_value.if condition_ref true_label false_labelwherecondition_refis something that can be used to get a value, likeconst value_literal,local_ref norarg nand thesetrue_labelandfalse_labelare created with special command.labelcommand marks this line by a name relevant to function scope, likelabel 1 some_aliasor simplylabel 2when it is not necessary to know or display in debugging the label name. Thetrue_labelandfalse_labelarelabel_ref nexpression orlabel_ref aliasexpression. Technicallylabelis same asno_opcommand.no_opwell it just does nothing and can be used for empty code blocks, and at leastno_opshould be included into function body- the
const_ref nwould reference a constant, let it number or string, or other scalar value known at compile type, let it be property name, class name, a0inn > 0expression or a string from template like"Hello ${name}". undefined,null,trueandfalse, as0and1are always known variants usable where expression should be, viaconst_ref nullorconst_ref true. Binary representations can have special bytecodes for such cases forconst_ref trueorconst_ref false.return valueandreturn_voidare return commands, wherereturn_voidis just more explicit analog ofreturn const undefined. Returning from the topmost code of module would be an invalid operation.call function_value receiver_refwould call a function, going into it, and return value would be assigned to value receiver, let it be local value, or object property referenceprop_ref obj_value prop_name_value.new_obj constructor_value receiver_refwould call a constructor with all that neuances and returned value would be assigned toreceiver_refwhich is same thing as forcallcommand.new_array length_value receiver_ref– allocates new array, usually with 0 length, what can be optimized withnew_empty_array receiver_reftype of command- There is no such thing as
ignored return value, technically it implemented bydiscard_refwhat 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_installandpre_installscripts. The whole process of library installation intonode_modulesshould consist of copying a library file into node_modules under special paths like@scope/library/compiled.jslibraryorlibrary/compiled.jslibrary. This can include some check for semantic correctness, like do not useconst undefinedorconst nullasobj_valueinprop_getorprop_set, or thelabel_refshould point to label declared in same function withlabelcommand, or never should usediscard_refas 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

Leave a Reply