openhab-scripters / openhab-helper-libraries

Scripts and modules for use with openHAB
Eclipse Public License 1.0
88 stars 69 forks source link

Use a JS module system for JS rules #267

Closed jpg0 closed 4 years ago

jpg0 commented 4 years ago

Is your feature request related to a problem? Please describe. I find the custom library loading code frustrating because:

Describe the solution you'd like

Describe alternatives you've considered

5iver commented 4 years ago

I'm pretty certain these will not be possible, as we need to stay native Nashorn for javax.script. Even if it was possible, I do not feel it would be worthwhile implementing a module system, since JDK9/ES6 is very close and will change everything.

jpg0 commented 4 years ago

So I was thinking of just something basic which meant that you could use a loader syntax. To see if it was possible, I just created something basic so that you can write code like this:

In script:

load(Java.type("java.lang.System").getenv("OPENHAB_CONF")+'/automation/lib/javascript/personal/loader.js');

var foo = require('foo');
foo.bar();

In library:

exports.bar = function(){ ... }

I can create a PR to add the loader.js file if you like / are interested? ps.I am also hoping for JDK9 but from the actual movement I see in OH I'm not confident it'll happen for a while.

5iver commented 4 years ago

Here are the outstanding issues with JDK11... it's close! And IIRC, there are less with JDK9.

If you are wanting to actively develop the JS helper libraries, I definitely do not want to hold you up! I'm not much of a JS developer, so hopefully we can get some feedback from @lewie or @Confectrician. I see the benefit of what you are proposing, but my gut is telling me to wait for ES6, or we will have a lot of rework.

Martin-Stangl commented 4 years ago

@jpg0, I have a question for clarification: Is it correct to assume that the library file containing the code exports.bar = function(){ ... } is called foo.js? And is it also located in /automation/lib/javascript/personal/? Or is there some magic going on in loader.js that maps foo somehow to something?

In general using modules is a great idea. But it would be a major rewrite and keeping backwards compatibility to the current libraries will be quite some effort - probably more than it is worth..

jpg0 commented 4 years ago

@openhab-5iver I'm glad that you're more optimistic than I am! It was exactly that issue (and the lack of progress on it + it's dependencies) that made me think it will be a while!

@Martin-Stangl yes that is all correct. Loader is not magic at all, I just made some minor modifications to an existing pure-JS one: https://gist.github.com/bspaulding/1386829. Saying that, it does not currently prevent libraries polluting the global namespace, but well-behaved ones should work fine (this may be possible using loadWithNewGlobal in Nashorn, but I haven't looked into it).

The reason that I am suggesting doing this now is that: 1) It's going to have to happen at some time (to provide a decent JS env) 2) It's only going to get harder - as changing the libs/syntax is the bit that is the most painful, and more code using the existing helper libs is written all the time 3) Selfishly, I'm planning to switch over this for my scripts now, regardless of what happens here :)

As for backwards compatibility, I agree that this is the hardest part. But it will only get harder. There are also quite a few options. For example, maybe the cleanest would be to deprecate the 'core' directory and create another, such as 'common' or 'modules' or something, then just leave stubs in core which 'require' their associated modules, and hoist the exports up into the global namespace. This means that all work can proceed with no ongoing work for backward compatibility and the new work inherits none of the legacy. Also remember that there's no technical reason preventing both approaches being used concurrently, even in the same file. (Just not for the same lib.)

I guess that one option is for me to do this in a fork (including porting existing libs + maintaining backwards compatibility) and you can see if you're interested in pulling it in - as I want to do it for my scripts anyway - but if this is a something that is very unlikely to be adopted then I wouldn't bother with the compatibility layer if it's only for my scripts anyway.

jpg0 commented 4 years ago

Quick update: I've ported most of the existing libs over to a CommonJS style. I decided to just replicate them into a /modules folder and leave the existing ones there: https://github.com/jpg0/openhab-helper-libraries/commit/e246da5284db58beb0db5a2f19814cb6cfc78756

What I have learnt:

5iver commented 4 years ago

I will provide more detailed feedback when I review the PR, but wanted to respond with some of the larger issues as soon as I could.

the 'scriptExtension.importPreset' commands just dump everything into the global namespace.

This is by design. What are you thinking should change?

jpg0 commented 4 years ago

Please submit a PR with your changes. It will be much easier to diff the files and view the changes that you have made,

Sure! The reason that I did this was to give very high-level feedback on the approach, as I don't regard this as PR-quality yet. So thanks!

Why add a modules directory? The core JS libraries are already in /automation/lib/javascript/core/, so I don't see why the core directory couldn't be used instead. This also keeps the directories aligned with the Python libs.

The challenge here is backwards compatibility. There is currently no abstraction in the loading of libraries; consumers are encouraged to literally construct the file path and directly load/inline the library code. This leaves a few options:

The core.logutil lib will need to be renamed to match the Python core.log lib. In the future though, this lib (Python and JS) will be replaced with an OH core action or actions, similar to logInfo, logDebug, etc.

Sure, no problem.

Remove test.js.

Sure, as I mentioned this isn't yet PR-quality.

Please include before and after usage examples to show how the libraries would need to be used after this change.

Where would you like the usage examples? Maybe port the example scripts as part of the PR? Ultimately they are standard CommonJS, so the change isn't large, just that to use a function bar in module foo, e.g. it changes from:

load('foo');
bar();

to

var foo = require('foo');
foo.bar();

For symmetry with the Python libs, WDYT of renaming loader.js to init.js?

Sure, no problem.

the 'scriptExtension.importPreset' commands just dump everything into the global namespace.

This is by design. What are you thinking should change?

I was thinking that instead of inlining the code when calling that, that it would return an object which contained the exported functions/variables, just like CommonJS. I do appreciate however that there are cases where consumers would want everything inlined to cut down on characters in simple scripts, so this should remain an option (which is possible if returning an object, merging the object into global level, but picking it back out isn't).

ps. Won't get to this for a few days as I'm away, but I'm actively working on it as I port my scripts from Xtend to JS so I'll be back on it then.

Martin-Stangl commented 4 years ago

@jpg0 just a heads up/warning: I made a pull request #268 for a core/log.js. So this part might look significantly different to your core/logutil.js library soon. Well probably not significantly for what you are doing as it should convert as easy into a module as the other libraries.

jpg0 commented 4 years ago

Added PR: https://github.com/openhab-scripters/openhab-helper-libraries/pull/270

I think I addressed all your points @openhab-5iver, but happy to make further changes. I also managed to implement isolation to prevent libraries from adding to the global namespace.

@Martin-Stangl no worries about #268 - I ported it immediately and am now using it myself.

jpg0 commented 4 years ago

Forget that PR, I've updated to #271

Providing isolation between libraries was getting too complex, and was then starting to fight with the importPreset approach which is unisolated by design. I've reverted to a simpler design which forgoes isolation.

jpg0 commented 4 years ago

I'll close this issue, as it's now very out-of-date as all this has been solved and more.

I now have:

https://github.com/jpg0/openhab2-addons/tree/master/bundles/org.openhab.automation.module.script.graaljs