nim javascript backend generates a single .js file:
https://nim-lang.org/docs/backends.html#nimcache-naming-logic-nimcache-and-the-javascript-target
There are no other temporary files generated, the output is always a single self contained .js file.
could it use the new javascript ES6 modules (say as an option)? It's supported on all modern browsers
or is outputting to webassembly making all this obsolete?
I wondered how difficult it would be to create a JS backend that converts the Nim AST to the ESTree spec AST and then use the resulting ESTree spec AST to generate the Javascript with a tool like ESCode Gen.
The benefit is generate very clean JS code in any desired target version. It would also mean access to other JS module tooling. The downside is its probably a lot slower than the Nim JS backend.
Sure it could use ES6 modules but that would be quite some work to implement and what would be the gain?
For a completely nim-based app, not a lot. If you're incorporating the result into a larger site or trying to do something like code splitting I expect the js-based tools will work better with the output split into modules but I wouldn't be confident advocating for it without testing stuff out. It's something I've thought about exploring but there's much lower hanging fruit in terms of js output.
you could already run the existing JS through a prettifier, no? I think the results would be the same.
The output of the js backend isn't particularly elegant and I think it'd need a cleanup pass before it'd look good after a prettifier. I hacked up the jsgen part of the compiler last month to output closure compiler compatible code. As long as I -d:release to get rid of the frame tracking cruft and run the code through closure advanced mode I can get reasonably good code out of the js backend.
The major remaining piece that's holding back the output js from being what I would consider to be good is nimCopy. The need to reflect on types causes jsgen to emit the full set of types. Using the Karax todoapp as an example, after passing through closure advanced ~60% of the app is this type information. I estimate the output would be ~16k instead of the current 96k and the 5.6k gzip gets Karax into the competitive range with js micro frameworks. Further, nimCopy is polymorphic and mutually recursive and I'm pretty sure that's preventing JIT inlining. I think the compiler has enough type information to emit multiple monomorphic implementations which should eliminate the need to have the type information in the output for some fraction of the nimCopy uses and I think a lot would get inlined and eliminated by the closure compiler's optimizations.
I got tripped up on working on this because nimCopy is used to implement a number of behaviors beyond just deep copying (something with return values, etc) and I don't have a firm enough grasp on nim semantics to know if I'm breaking stuff or not. I got distracted by another project and never came back around to working on Nim's js output. I think there's likely a real need for the full polymorphic mutually recursive version but I have a hard time telling.
The lesser remaining piece is some way to set all strings in a module to be parsed as cstrings instead of nim strings. I get why the decision was made to have nim strings as the default but there's a varying (depending on app size) amount of activity at startup that's simply converting string literals from JS into nim strings only to convert them back to js strings for use/display. The converting back and forth is inefficient but I really don't want the activity at startup because it delays time to interactive.
The compiler has a nice tracking system to convert cstrings into nim strings for things that need nim strings so everything works fine if you remember to declare everything to be a cstring but it requires vigilance on the part of the developer. I think a large fraction of nim client apps wouldn't need nim strings at all.
With those out of the way, I think the biggest gains would be adjusting Karax. I started hacking on the js backend because I think Nim has enough type information and compile time programming that I could separate out the non-changing nodes from the changing nodes, compile the static parts to html templates, and get pretty significant wins in pretty much every metric but I got sidetracked by the js output.
For compiler tweaks beyond the runtime ones I outlined, the top of my list would be source map support, which would allow the elimination of the frame tracking stuff. I'd want to double-check the other runtime functions to make sure they're efficient. There's also a mismatch in js language support. The example that comes to mind is the code emitter outputs a set of typed array polyfills but doing async stuff emits async/await and nothing supports that without also supporting typed arrays. I noted a few other things like this but the issue is more of a social issue than a technical one.
Well, that went on longer than I anticipated when I started. I wanted to have more to show and be able to make a firmer commitment to fixing things before complaining but the post is written so I might as well complain now.
Sorry for necroing this thread - but that web components are close to reaching maturity, I think ES6 constructs are going to be crucial to Nim's JS backend moving forward.
For instance - https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements - rely on ES6 classes.
I'm going to start working on some macros for generating ES6 classes - if I come up with anything usable I will share.
I've been working on this in my spare time over the past few evenings. I'm keeping this gist updated w/ my progress - https://gist.github.com/zacharycarter/ccdd4e5d868e9a9acce6e15390afed49
Please let me know your thoughts so far - I'm basically working through: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes - although I know I have a lot of holes in the macro due to not checking edge cases. I will eventually write a testbed for this thing and try to cover as many as I can.
Would there be interest in turning this into a library? I'm sure there is other ES* sugar that would be nice to have in the JS target.
@timothee I will as soon as I am able. This library is being produced for a work project, and I need to consult with my supervisor / his boss, to ensure that we can publish the libraries I've written for this project, on github (under the company name). If not - I will publish them personally and just consume them that way at work.
Quick update for now - I have ES6 classes working along with getters, setters, prototype methods and static methods. I haven't really put the macro through its paces yet, but I intend to do that shortly.
I also have basic custom elements working as well - https://gist.github.com/zacharycarter/6af93331f62dda6f463c69588d3f9c6c
So that portion of the web components spec is starting to be worked on as well.
well... that's mostly a feature that benefits JavaScript people rather than Nim users. ES6 modules may collaborate modern js bundlers better. For top-level functions there are still steps needed to expose them to bundlers.
maybe I should clarify for my last comment, I was not talking about all ES6 features, but only talking about ES modules(https://nodejs.org/api/esm.html), which JavaScript community is moving to.
for my own need, I would like Nim to compile code that I can turn into a library or an npm package directly. every modern js bundler now recognizes ES modules well.
I'm not against the metaphor of treating js as wasm, but wasm also uses an export instruction. https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format#Calling_the_function
(module
(func $add (param $lhs i32) (param $rhs i32) (result i32)
local.get $lhs
local.get $rhs
i32.add)
(export "add" (func $add))
)
like just support compiling to export function:
proc f1() {.exportc.} =
echo 1
export function f1() {
console.log(1)
}
jsexport foo, bar, baz
# export { foo, bar, baz };
constant ::= "This is a JavaScript const".cstring
# const constant = "This is a JavaScript const";
example2(arg0, arg1: int) {.codegenDecl: arrow.} => console.log "JS Arrow Function"
# const example2 = (arg0, arg01) => { console.log("JS Arrow Function") };
iife:
# (() => {
echo "Hello Immediately Invoked Function Expressions"
# })();
You can make an ES6 module in Nim easily.
proc sayHello() {.exportc.} = echo "hello"
proc sayGoodbye() {.exportc.} = echo "goodbye"
{.emit: "export { sayHello, sayGoodbye }".}
The macro facilities of Nim seem very impressive. So maybe ES6 module support can be done simply as a Nim library, along the lines you describe here? My main concern here is not to duplicate generated code: If I make two modules in Nim, A and B, and A imports B, and B imports A, then of course two ES6 modules A and B should be generated which only contain the code corresponding to Nim module A, and B, respectively. As far as I can see right now (and I am an absolute newbie in Nim, so I don't see much, probably), what could easily be done is to generate two ES6 modules A' and B', where A' includes all of A and B and exports only interface A, and B' also includes all of A and B but exports only the interface of B. But that's obviously not very nice, or scalable, for that matter.
I actually quite like Javascript's module system. I am more of a Standard ML/Swift/Scala kinda guy, but JavaScript is starting to look really good in terms of being a solid operating system on which to build on. And it seems to me that Nim's package and module system is actually a great match for ES6 modules. Furthermore, types could be optionally encapsulated by wrapping them in class wrappers, to guarantee data type invariants even at runtime, if so desired. Together with proper support for and integration of WebAssembly out of the box, Nim would be one hell of a value proposition for that operating system, pretty much impossible to ignore.
But yeah, I can see how that is a lot of work. In principle, do you think this could be done via a library?
So once WebAssembly can call the DOM directly, you would consider Nim's JavaScript support unnecessary?
I would love to say "yes" but there is a gigantic amount of JavaScript code out there that we can interface with quite easily thanks to the JS backend.