musictheory / NilScript

Objective-C-style language superset of JavaScript with a tiny, simple runtime
Other
50 stars 5 forks source link

Thoughts: ES6 integration features #63

Open IngwiePhoenix opened 8 years ago

IngwiePhoenix commented 8 years ago

This is not really a proposal, but rather a bit of an idea that I had.

While working with OJ, I had faced, back and forth, the issue of how to share classes. Since I use OJ mainly on the client using WebPack and my transformers/loaders, I would always have to somehow get the various files to come together.

My current aproach is to use a custom preprocessor to build my code. It works like the C one, but for OJ.

Since we are nearing proper ES6 support, I had something like this on my mind:

@implementation Foo
// ...
@end

export default Foo;

and

import Foo from "classes/Foo.oj";

@implementation Bar : Foo
// ...
@end

The options we have for implementing this:

However, OJ currently knows of the files "statically", as it comes from the array of files. So, when we find an import statement, we should consider adding that file to the files list and dropping the statement alltogether. Result:

@implementation Foo
@end

// removed: import FooClass from "Foo.oj";
@implementation Bar : FooClass
@end

The JavaScript of this "import" would be something like

var $oj_cls_FooClass =  $oj._cls.Foo;

That would, onc eagain, require us to read the AST and generate a changed statement for what used to be an import atatement.

One example which would be very close to my project's workflow:

import BIRD3Editor from "BIRD3/Extensions/MarkdownEditor";
// snip
@implementation App
-(void)run {
    if($("body").find("[data-editor]").length != 0) {
        var editors = $("body").find("[data-editor]");
        for(var i = 0; i<editors.length; i++) {
            [BIRD3Editor initWithElement:editors[i]];
        }
    }
}

These are only thoughts, so feel free to ignore this if you wish to :). I just thought I'd share what I had in my mind.

iccir commented 8 years ago

While working with OJ, I had faced, back and forth, the issue of how to share classes. Since I use OJ mainly on the client using WebPack and my transformers/loaders, I would always have to somehow get the various files to come together.

I still haven't quite grasped the issue of sharing classes. What we (both my company and my friend's startup) do is concatenate all .oj files together and compile as one unit. We have a file watcher in place, as soon as an .oj file is modified, it spits out a new webapp.js file with the compiled contents. Remind me again why this doesn't work in your case? (You can use your preprocessor to figure out which files to include, and then compile all files at once).

In the above example, All you should need is some kind of #import BIRD3/Extensions/MarkdownEditor. Your preprocessor would see this, add MarkdownEditor.oj to the compile list as a requirement for App.oj, and know to compile both in the same compilation unit.

IngwiePhoenix commented 8 years ago

I still haven't quite grasped the issue of sharing classes. What we (both my company and my friend's startup) do is concatenate all .oj files together and compile as one unit. We have a file watcher in place, as soon as an .oj file is modified, it spits out a new webapp.js file with the compiled contents. Remind me again why this doesn't work in your case? (You can use your preprocessor to figure out which files to include, and then compile all files at once).

To build my front-end code, I use the WebPack bundler. To bundle files, it scans the input for any require - and now import - statement and picks up what it reads. That way, it traverses through the entire code by picking up these statements, makes a list of files, and bundles them into an environment that mimics NodeJS - or, commonly known as "CommonJS". It also supports the AMD specification, but that one is rarely used. WebPack also supports asynchronous require calls - but in the end, it all boils down to the fact that multiple files get put together into one.

However, for this modular aproach to work, it wraps all the modules into a function and assigns it an ID. For instance:

function(module, exports, __webpack_require__) {
    // module code
}

All the require statements get translated to become __webpack_require__ statements instead. So what once was

var oj = require("oj/src/runtime");

could become:

var oj = __webpack_require__(2);

As you can see, with all the OJ code being wrapped per-function, I believe you can see what issues it proposes. The classes do indeed get registered into the global $oj_oj object - however, if webpack decides to put the required module code below the one that needs it, the class would not be defined.

That is why I built a C-style preprocessor to put the files together, while still being able to utilize WebPack - which has many other features. For instance, I can litterally require a SCSS file, and it'll get transformed into CSS and split into a different, global CSS file. This also works with other file types. My OJ loader does the same - it compiles OJ code and returns it to WebPack. The compiler state is attached to WebPack's, so that it can keep track of classes and alike. It works pretty well. But importing and exporting - or generally, sharing classes across modules opposes a strange behaviour sometimes.

Because: OJ actually may not know of a class during compilation.

That is the kind of problem I have, and why I built custom tools to tackle it. :)

I could add the #import directive, indeed - and I might do that, since my re-structure of the codebase also affected the OJ files.

IngwiePhoenix commented 8 years ago

I should add, that the following code does not work in a RequireJS/CommonJS environment.

Foo.oj

@implementation Foo
+(void) doIt { console.log("Doing something..."); }
@end

App.oj

require("oj-node"); // Allow .oj files to run
require("./Foo.oj");

// This will fail: Foo not defined
[Foo doIt];

// But this, will work.
@class Foo;
[Foo doIt];

However, module.exports = [Foo class]; will actually work.

var Foo = require("./Foo.oj");
[Foo doIt];