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

Steps towards implementing require() in Carlo #149

Open
trusktr opened this issue Apr 5, 2019 · 12 comments
Open

Steps towards implementing require() in Carlo #149

trusktr opened this issue Apr 5, 2019 · 12 comments

Comments

@trusktr
Copy link

trusktr commented Apr 5, 2019

Hello, I'm coming from Electron, and would like to be able to import Node.js modules on the front end.

Some questions:

  • Is this architecturally possible, or is there a limitation? Is data from Node passable by reference? (If data from Node is always serialized and not passable as a reference, then that puts up a road block)
  • If possible, what steps might I take towards starting?
@trusktr
Copy link
Author

trusktr commented Apr 5, 2019

Does a return value from a call to a function added with App.exposeFunction get returned serialized, or by reference? 🤞

@trusktr
Copy link
Author

trusktr commented Apr 5, 2019

Yeah, looks like it serializes. The following example results in an error.

example.js:

const carlo = require('carlo');

(async () => {
  // Launch the browser.
  const app = await carlo.launch();

  // Terminate Node.js process on app window closing.
  app.on('exit', () => process.exit());

  // Tell carlo where your web files are located.
  app.serveFolder(__dirname);
  
  class Box {
      constructor(x, y) {
          this.x = x
          this.y = y
      }
      
      getSize() {
          return [this.x, this.y]
      }
  }
  
  function makeBox(size) {
      return new Box(size[0], size[1])
  }

  // Expose 'env' function in the web environment.
  await app.exposeFunction('makeBox', makeBox);

  // Navigate to the main page of your app.
  await app.load('example.html');
})();

example.html

<script>
async function run() {
    const box = await makeBox([10, 10])
    console.log(box.getSize()) // ERROR
}
</script>
<body onload="run()">

Results in

Uncaught (in promise) TypeError: box.getSize is not a function
    at run (example.html:4)

I suppose we'd have to re-implement modules like fs on the browser side so they can send the data to/from with serialization if we go that path.


Is there another way? Seems like this is where Electron wins at the moment, and there's not much incentive to switch over just to save hard disk space from re-using Chrome binaries.

@trusktr
Copy link
Author

trusktr commented Apr 5, 2019

Ah, README says

Node v8 and Chrome v8 engines are decoupled in Carlo,

So obviously it's probably not possible to just pass references, if there's two engines running.


Maybe the opposite is a better idea: implement a DOM interface on the Node side that can proxy to DOM on the browser side? In either case, there will be gotchas and complications.

It'd be nice if somehow Chrome could use Node's v8. Is that possible?

@pavelfeldman
Copy link
Collaborator

You can use rpc to solve some of your problems.

@trusktr
Copy link
Author

trusktr commented Apr 5, 2019

@pavelfeldman Thanks, that looks like it! Interesting that it is possible to wrap reference across engines like that.

So how do we use it? Where do I get the rpc module from? Where is example.html? How do I get the rpc handle in the browser? Where's an example I can run?

@trusktr
Copy link
Author

trusktr commented Apr 5, 2019

Ah, I see

  const [app, term] = await carlo.loadParams();

in the terminal example's index.html. Nice!

By the way, why is that Terminal example so slow (I mean moving around in vim is really slow, for example)? In Atom text editor terminals, it is much zippier (running in Electron).

Is it the xterm.js's UI rendering in particular, or is it the rpc communication?

@trusktr
Copy link
Author

trusktr commented Apr 5, 2019

Alright, gave it a try, but no luck:

example.js

const carlo = require('carlo')
const { rpc, rpc_process } = require('carlo/rpc')
const requireHandle = rpc.handle(require)

main()

async function main() {
    // Launch the browser.
    const app = await carlo.launch({
        bgcolor: '#2b2e3b',
        title: 'Require all the things',
        width: 800,
        height: 800,
    })
    
    app.on('exit', () => process.exit())
    app.serveFolder(__dirname)
    app.on('window', win => initWindow(win))
    initWindow(app.mainWindow())
}

function initWindow(win) {
    win.load('example.html', requireHandle)
}

example.html

<script>
async function run() {
    const [require] = await carlo.loadParams()
    
    require('fs').then(fs => {
        console.log(fs.readFileSync('example.html'))
    })
}
</script>
<body onload="run()">

results in

Screen Shot 2019-04-05 at 3 57 30 PM

@VandeurenGlenn
Copy link

VandeurenGlenn commented Apr 11, 2019

@trusktr I try avoiding rpc, mostly by doing something like below.

import fs from 'fs';

...
await app.exposeFunction('fs', (desired, args) => {
  return fs[desired](args.path);
});
...
...
<script>
  const run = async () => {
    const response = await fs('readFileSync', {path: 'README.md'});
  }
</script>
...

@trusktr
Copy link
Author

trusktr commented Apr 24, 2019

I'd like to run existing Node.js code without modification.

Did I do my attempt correctly? Is there a way to do it? (existing code expects require() to be synchronous)

@trusktr
Copy link
Author

trusktr commented May 28, 2019

Does the app.exposeFunction tool (de)serialize information between the contexts under the hood? If so, that's a no-go then.

@trusktr
Copy link
Author

trusktr commented Jul 4, 2019

Ah, ok, so no. Is there any plan for future Node.js support? Or is the plan to keep the web context purely a web context for always?

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