We eventually need to get to the issue of how we want to handle multiple files. There are, as I see it, a few approaches to this:
1. Copy Node.js
This is the most brittle approach, however it also ties in easiest with our JavaScript target since all source files can be compiled directly to corresponding JavaScript files.
The big downside to this approach is that require
and module.exports
provide zero type information, so any uses of values from a required(...)
module (internal or external) would have no type information (just a ton of Any
types).
We could perhaps develop a hybrid system on top of this. Where Hummingbird files would be loaded with a different keyword/syntax and those files would expose their exported values via a complementary interface. For example:
// Importing Node module and local Hb module
var express = require("express")
var something = import("./something.hb")
let Foo = "bar"
func baz () -> String { return "#yolo" }
// Exporting something in this file
// (compiled down to module.exports)
export Foo, baz
2. Roll our own with different design
Node enforces strict boundaries between files, but we don't necessarily need to follow the same path. I'm very open to a "unifying" system that compiles files together into a unified space instead of the deep tree of modules that Node builds.
The disadvantage of this approach is that we don't always get the same file-to-file compilation path as the former. The latter is that we can decide exactly how we want to design our system free from any bounds imposed by Node.js or any other JavaScript runtime environment. Although this means more work for us and less closely hewing to the JavaScript target, I'm in favor of it for the freedom that it gives us to design our system, APIs, and ability to target other runtimes (PyPy, native, etc.).
A trivial, contrived example off the top of my head:
// Only available when targeting Node.js compilation
var express = require_node("express")
// Loading from Hb package system and local file
var something = import("something")
var other = import("./other")
let Foo = "bar"
export Foo as FOO
Final note: import()
vs export
In these examples I use import()
with parentheses and export
without. The reasoning for this is that, in my mind, importing is an imperative action: you are telling the compiler to do something right now. In contrast, exporting is a declarative action: you are telling the compiler something about this file. This is just a thought and I am in no way attached to these designs.