Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QuickJS: JavaScript modules #15

Open
3 tasks
JerrySievert opened this issue Oct 9, 2019 · 27 comments
Open
3 tasks

QuickJS: JavaScript modules #15

JerrySievert opened this issue Oct 9, 2019 · 27 comments

Comments

@JerrySievert
Copy link
Collaborator

JerrySievert commented Oct 9, 2019

wondering what you think about adding a javascript module loader?

modules would be able to be loaded from either a specific defined path, or from a configured path, either absolute or relative.

a decision would likely need to be made whether to use CommonJS or ES6 modules.

alternately, enabling the std and os modules in QuickJS would allow access to the filesystem, local machine, etc.

Current proposal as of 2020-06-11 (edit by @AndrewBelt )

  • Add a folder called js_modules/ or something in Prototype's plugin directory.
  • Bundle several open-source JS libraries in the distribution, such as https://mathjs.org/.
  • Implement JSModuleLoaderFunc or however it works in order to allow scripts to use import "whatever" to load a script/module from js_modules/.
@AndrewBelt
Copy link
Member

This makes it difficult to bundle the script into a sharable patch file.

@JerrySievert
Copy link
Collaborator Author

I realize that, and it was copying/pasting modules into my script to share that made me make the proposal.

while it does make that use-case a bit more difficult, it makes clean code and extendability much easier.

@JerrySievert
Copy link
Collaborator Author

I'll create a PR with the change to add modules so we can have a more "informed" discussion. there would still be a few questions to work out, but might as well get the discussion moving forward.

@JerrySievert
Copy link
Collaborator Author

amusingly, QuickJS has 2 modes of interpretation:

  • modules, in which import works
  • global, in which having access to the data in the global namespace works

I've sent an email to the devel list, to hopefully get a sane response back about, well, you know, being able to both import modules and run code.

@AndrewBelt
Copy link
Member

I'm not really interested in the script engine implementation but the Prototype Module implementation. Scripts are included in patches so people can share their patch so others can try their script. Including multiple scripts, each with a path, in the module data would be a mess.

@JerrySievert
Copy link
Collaborator Author

the overall issue is that without having a mechanism for modules to be loaded, there ends up being a ton of bespoke "modules" and code duplication that becomes very unmaintainable very quickly.

it's possible to build complex and powerful audio applications in javascript, and vcv-prototype is definitely fast enough to be able to run them, but hampering the implementation makes this much more difficult. it becomes the same as removing "#include" from c/c++: possible to do, but untenable in the end.

being able to access the huge module ecosystem, and share audio or vcvrack specific modules (like the CV module I copied/pasted in my community example) ends up making the whole community stronger.

@AndrewBelt
Copy link
Member

I'm also not really interested in the motivation because I understand it well. I repeat: I'm interested in the "Prototype Module implementation".

@JerrySievert
Copy link
Collaborator Author

I'm interested in the "Prototype Module implementation".

and I'm interested in beer. without expanding on that I have to guess that you mean that you're interested solely in people making small things and sharing them.

@AndrewBelt
Copy link
Member

AndrewBelt commented Oct 17, 2019

Perhaps I'm being too vague in what I mean by that. For me to approve this proposal, I'd like to see what changes will need to be done to src/Prototype.cpp and Prototype's JSON "data" tag to make user-included JavaScript modules sharable. This is necessary to keep the promise "You can share your script by sharing your patch."

Separate idea: Instead of allowing users to download and include their own JavaScript modules, why not just bundle popular ones in the Prototype package? I bet we could find 10+ useful open-source BSD/MIT/GPL/etc libraries like https://mathjs.org/, https://github.com/corbanbrook/dsp.js/, etc. to include in a dep/js-modules folder or something. Then, it's ever easier for users to use common JS libraries, because they can skip the process of downloading and including them. We can happily accept recommendations of libraries from users to eventually collect everything everyone needs.

Users could add to their script

import math from 'mathjs'

or

const math = require("mathjs")

or however we do it. Since mathjs would be included on everyone's computers, the patch would not need to include the module source code itself.

As a side-effect of this proposal, I'm happy to then allow users to add their own JS modules and import/require them, because the action of doing this should suggest to them that their script becomes "unsharable". In other words, I accept your proposal to add JS modules, under the condition that we go out and find dozens of JS libraries to bundle so scripts remain mostly sharable.

@JerrySievert
Copy link
Collaborator Author

Perhaps I'm being too vague in what I mean by that.

hence my comment :)

I'd like to see what changes will need to be done to src/Prototype.cpp and Prototype's JSON "data" tag to make user-included JavaScript modules sharable.

to use modules, nothing - the questions that arise from this are a little more subtle imo:

  • which mechanisms should be allowable for including modules:
    • http based modules
    • files
      • from node_modules or some other npm or yarn path?
      • from a Prototype supplied module
  • should we reinvent a packaging system?
  • which module system should be supported?
    • CommonJS
    • ECMA

alternately, a change could be made to Prototype.cpp to add a module path. this would still require users to install modules, but give them access to a setting to specify the path.

I'm also ok with a Prototype module distribution as part of the distribution, but it would mean added cognitive load. while I'm guessing you wouldn't be able to take that load, it wouldn't be much for me.

this issue was opened to gauge interest, not to provide a concrete proposal, just so we're on the same page. opening a PR would have solidified more of that.

@AndrewBelt
Copy link
Member

AndrewBelt commented Oct 17, 2019

which mechanisms should be allowable for including modules:

Don't include any of those. Just mathjs, etc. Anything that can run in a browser. Nothing Node-like such as os or std from QuickJS.

should we reinvent a packaging system?

No. Just install mathjs, etc to some directory so that import can access them, and commit the source. You can add them by downloading them manually, use git submodules, npm, Makefile dep targets, or any other method to add them.

which module system should be supported?

Probably ECMA if possible.

a change could be made to Prototype.cpp to add a module path.

I disagree. It should be hard-coded to asset::plugin(..., "js-modules") or something, which would include only bundled modules unless a user messes with the directory.

@JerrySievert
Copy link
Collaborator Author

I disagree. It should be hard-coded to asset::plugin(..., "js-modules") or something, which would include only bundled modules unless a user messes with the directory.

my issue with this is if there is work in progress by the user, and it is overwritten due to a module update. if module updates were completely non-destructive, this wouldn't be an issue (I have not investigated whether module updates remove the old module and install the new one, or if it is an overwrite preserving content).

Probably ECMA if possible.

actual lowers the number of usable modules, since ECMA is about a decade behind the module curve.

Anything that can run in a browser. Nothing Node-like such as os or std from QuickJS.

you might want to qualify that a little more, browserjs exists, and having been neck deep in plv8 I'm way too aware of how easy it is to wedge a package.

No. Just install mathjs, etc to some directory so that import can access them, and commit the source. You can add them by downloading them manually, use git submodules, npm, Makefile dep targets, or any other method to add them.

I'd love a canonical answer on my first point without having to trace through code.

@AndrewBelt
Copy link
Member

my issue with this is if there is work in progress by the user

The plugin updater merely overwrites modules, so it's fine. Question: Can the module loader escape from the module path, e.g. require("../x.js") or require("/x.js")?

actual lowers the number of usable modules

Okay, then CommonJS. I figured there was maybe something built-in that transformed module.exports to ECMA exports.

you might want to qualify that a little more

My clarification is "Modules should be pure JS, not native". os obviously needs to be native because you can't break out of the sandbox with pure JS. Mathjs, etc are written in pure JS.

I'd love a canonical answer on my first point without having to trace through code.

I don't know what you mean by this, but can't you just find the library script and copy is to js-modules manually? For mathjs, that'd be https://unpkg.com/[email protected]/dist/math.min.js.

@JerrySievert
Copy link
Collaborator Author

The plugin updater merely overwrites modules, so it's fine.

great, that's what I was worried about.

I don't know what you mean by this

the first point is ^^^ so answered.

Okay, then CommonJS. I figured there was maybe something built-in that transformed module.exports to ECMA exports.

ha. no. that would be too easy. browserjs manages to convert between node and browser modules, and babel does a lot of transformation as part of a build system, but if you're looking for any real commonality then prepare to sigh and roll your eyes.

Can the module loader escape from the module path, e.g. require("../x.js") or require("/x.js")?

yes, no. depends on how the module loader is written: it looks like you're wanting a sandbox - no problem, it's just a matter of checking paths.

fine with pure js modules, and not including os or std: that's been my default for a long time.

@AndrewBelt
Copy link
Member

Okay, do we have all the answers we need then? This should require 0 changes to src/Prototype.cpp or src/ScriptEngine.cpp.
Let's go crazy with bundling JS libraries then. Could you add one as an example?

@JerrySievert
Copy link
Collaborator Author

Okay, do we have all the answers we need then?

probably not, but enough to proceed.

Let's go crazy with bundling JS libraries then. Could you add one as an example?

only one? geez. yeah, I'll get a couple added and some documentation. it'll take a day or so for me to get something I'm happy enough with for a require but I'll get a PR open when I'm done.

@gridsystem
Copy link

gridsystem commented Jun 11, 2020

I've just found this project and it looks like a lot of fun to experiment with!

Edit: I think I missed some of the nuances of this convo so I've deleted my message below. I see now that you're opting for a system where if you include a third party module you aren't able to share the script which seems like a good compromise!

Is this still in the works?

@AndrewBelt
Copy link
Member

AndrewBelt commented Jun 11, 2020

@gridsystem

I wonder also if you'd consider something similar to webpack

Can you give me a 60 second pitch of this. Explain what it is, how users would use it, etc.

@gridsystem
Copy link

Sure. (For anyone reading later, that quote is from my original comment where I wrongly assumed only modules included in the core would be supported)

Dev writes script. Dev includes module import Foo from 'foo'

Task runner (e.g. webpack, gulp, grunt or other) is running in terminal during dev. It watches for file changes and runs a compilation task when code changes. The compilation task bundles code and all dependencies into an executable script.

A separate final production script could be run after dev, e.g. npm run build or gulp build which would minify the bundled code, strip out console.logs or run any other desired optimisation to prepare for distribution.

@AndrewBelt
Copy link
Member

AndrewBelt commented Jun 11, 2020

@gridsystem I think you're confusing VCV Prototype for Node or something. Users don't use a terminal when running VCV Prototype.

I've edited the original post to reflect the current proposal that @JerrySievert and I converged on (if I'm interpreting our old discussion above correctly).

@gridsystem
Copy link

gridsystem commented Jun 11, 2020

Those task runners do run using node.js. You could leverage node.js as a development tool, it's a very common way to work.

Applying a normal JS workflow to this, it would work this way.

  • Set up a basic vcv-js-sdk repo, containing
    • A basic build script for bundling dependencies
    • A blank mymodule.js file
    • An empty dist directory
    • A package.json containing npm run dev and npm run build commands
  • Dev runs git clone vcv-js-sdk
  • Dev edits mymodule.js
  • Dev runs npm run dev in a terminal window
  • Dev opens dist/mymodule.js in Prototype module
  • When done developing, run npm run build
  • Copy the dist/mymodule.js file and share online, which includes all of the dependencies your heart desires.

@AndrewBelt
Copy link
Member

That's way complicated. I think the current proposal for modules is fine.

@gridsystem
Copy link

Do you know if any progress has been made or planned with the current proposal?

That idea is achievable without changing the module. I'm not sure if I have time coming up to throw it together, but it could be done as an independent project.

@AndrewBelt
Copy link
Member

AndrewBelt commented Jun 11, 2020

Do you know if any progress has been made or planned with the current proposal?

No. I'd guess it'd be a 10-20 line feature so it would be done in one sitting.

That idea is achievable without changing the module

I don't know what this sentence means. What is "the module"?

@gridsystem
Copy link

Cool! That's awesome that it's so achievable.

Sorry - module is a pretty generic term in this context I guess. I mean VCV eurorack module.

@JerrySievert
Copy link
Collaborator Author

jeez, try to sleep in one morning and you miss all sorts of conversations!

I'll try to jump in with a couple of comments:

Do you know if any progress has been made or planned with the current proposal?

I've had other projects (software and hardware, and other things that I make my living doing) going on, so I haven't given this that much priority, and it hasn't jumped up in priority due to a couple of things:

  • while fully featured (I can build javascript copies of most any module within the confines of the inputs, outputs, and knobs), this is still very much a prototyping tool. I don't have install/usage numbers, but I've seen very few people using it in the wild - more adoption would mean higher priority toward features

  • it's slow and cpu intensive. and I'm not talking about the javascript engine - in order to work, there's a lot of movement between c++ and javascript. while quickjs is faster than v8 crossing the c++/javascript membrane, it's still a bit of a hit. running an oscillator takes very little cpu time, but there's still all that overhead, contributing to lower adoption

what's actually required: deciding whether to use commonjs or es6 modules and writing a loader.

and here's a module that I wrote for a similar module to this from 0.6 (see https://github.com/JerrySievert/ProtoTools) - an LFO that I am hereby contributing as GPLv3 code (or whatever specific license that this repo adopts in the future):

function LowFrequencyOscillator ( ) {
  this.phase = 0;
  this.pw = 0.5;
  this.freq = 1;
}

LowFrequencyOscillator.prototype.setPitch = function setPitch (pitch) {
  pitch = Math.min(pitch, 8.0);
  this.freq = 261.626 * Math.pow(2, pitch);
};

LowFrequencyOscillator.prototype.setFrequency = function setFrequency (frequency) {
  this.freq = frequency;
};

LowFrequencyOscillator.prototype.setPulseWidth = function setPulseWidth (pw) {
  this.pw = Math.max(Math.min(pw, 0.99), 0.01);
};

LowFrequencyOscillator.prototype.step = function step (dt) {
  var deltaPhase = Math.min(this.freq * dt, 0.5);
  this.phase += deltaPhase;

  if (this.phase >= 1) {
    this.phase -= 1;
  }
};

LowFrequencyOscillator.prototype.sin = function sin ( ) {
  return Math.sin(2 * Math.PI * this.phase);
};

LowFrequencyOscillator.prototype.tri = function tri ( ) {
  var x = this.phase - 0.75;

  return (4 * Math.abs(x - Math.round(x))) - 1;
};

LowFrequencyOscillator.prototype.saw = function saw ( ) {
  return 2 * (this.phase - Math.round(this.phase));
};

LowFrequencyOscillator.prototype.sqr = function sqr ( ) {
  return (this.phase < this.pw) ? 1 : -1;
};

LowFrequencyOscillator.prototype.noise = function noise ( ) {
  return (Math.random() - 0.5) * 2;
};

module.exports = exports = LowFrequencyOscillator;

note that this is in commonjs - I just haven't taken the time to write the 10-20 line loader, which would be a welcome addition if you wanted to take it on inside the realm of the proposal @gridsystem

@gridsystem
Copy link

Haha I hope the snooze was worth it!

I would love to help with this, but I don’t know a thing about C.

I could put together a js bundler as I described, but it wouldn’t be worth doing if a native module loader is going to be included. If it becomes a more realistic choice, let me know and I can try to find some time. I relate completely to you having other commitments.

Really cool to see the osc code! Thanks for sharing. Would be cool to get it published on npm, I would gladly set up a repo and do that if you’d want.

@AndrewBelt AndrewBelt changed the title javascript modules? QuickJS: JavaScript modules Jul 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants