live-codes / livecodes

Code Playground That Just Works!
https://livecodes.io
MIT License
778 stars 66 forks source link

Programmatic engine? #294

Closed aehlke closed 1 year ago

aehlke commented 1 year ago

Hi, I'm interested in reusing this for a native iOS experience. Not for a full livecodes editor but for a feature involving Astro templates compiled via wasm, and other things. I would run it within a webview and have native UI.

Is there a good way to use livecodes as a library, rather than a full IDE? So that I could programmatically load in JS, WASM etc. and programmatically access the output. Thank you

hatemhosny commented 1 year ago

Hi @aehlke

Yes, there is quite a powerful SDK that allows embedding and communicating with playgrounds and provides lots of configuration options.

You can have a look at the documentation (which is work in progress)

SDK docs:

https://dev.livecodes.io/docs/sdk

Configuration:

https://dev.livecodes.io/docs/configuration/configuration-object

The SDK will eventually be published to npm when ready. Meanwhile you may try the development build:

https://dev.livecodes.io/sdk/livecodes.js

Please let me know if that is what you wanted. Feedback is highly appreciated.

aehlke commented 1 year ago

Thanks very much! I will investigate

aehlke commented 1 year ago

I have some early feedback in case it helps you. I haven't yet integrated this, still evaluating/designing.

Thanks

update: I will probably need to add a simpler way to load code via SDK. I'd rather not run a local webserver in app to provide code via URL. I'd like to be able to give a string directly via the SDK (which I'd post in via the app layer above the webview, in my use case at least).

I would also like to be able to add in local modules by providing their files or source somehow (a string of a tarred directory? I don't know what's best for inputting this...) for fully offline and private development.

hatemhosny commented 1 year ago

hi @aehlke,

First, I'd like to inform you that the SDK is now published to NPM.
You can follow instructions in the docs for usage: https://livecodes.io/docs/sdk/

I already have plans to officially support headless mode and add that to the SDK.
Meanwhile, you may just hide the container element and use the SDK to interact with the hidden playground.

for example:

container.style.visibility = 'hidden';
container.style.position = 'absolute';
container.style.width = '0';

or, of course, by CSS.

demo:
https://livecodes.io/?x=id/q5iapdukvnd

The getCode method may be what you need to get the compiled code and the result page HTML.

To load your code check: https://livecodes.io/docs/features/code-prefill

There is no problem adding multiple instances of the playground into the same page. You may want to use lite mode to improve performance.

However, please note that LiveCodes does not support having multi-file project with a file-system tree.
more info here: https://livecodes.io/docs/features/projects/

If you need a project with multiple files in a file-system you may want to use another playground. Examples include:

I hope this helps. Please let me know if you need any more help.

aehlke commented 1 year ago

Thank you, I will dig into your links. I highly value LiveCodes being offline and client side.

I think the multi-file approach is possible if I use your editor simply as an editor without preview to edit individual files from a package, and then have my app layer sync the re-packaged package such that the LiveCodes preview instance can run a snippet which instantiates the package that the other instances are editing. (In fact I'd like to use this preview instance not only for dev but also for production rendering of components in webviews in my app.) So it is my app layer's responsibility to shuffle data to the preview in realtime.

aehlke commented 1 year ago

One more piece of feedback: I'll need the results iframe to proxy postMessage (or an allow-list of postMessage handlers for security) back to the parent browser/app. I can probably add this in a fork/PR at least for my own needs but haven't attempted yet.

My specific use case: I'll use RxDB ( https://rxdb.info ) inside the results view with its Remote storage engine, which uses postMessage (or any other kind of async call) to sync data to something remote. My app will keep a small DB in sync with the webview this way.

Here's another project's example for how to proxy postMessage just for reference https://github.com/react-native-webview/react-native-webview/issues/1718#issuecomment-825468334

It's possible I've missed something and maybe something with customEvents.apiResponse allows this already, I couldn't quite tell from the source

edit - 2nd question: do you have plans for offline module imports? I'd like to be able to feed files or expose a file://-based directory to the webview to provide the modules offline. Am I able to use file URLs for loading npm etc. modules?

Thanks again. I hope to make money with my projects launching soon and would love to become a sponsor for your work. It's incredibly valuable and unique.

hatemhosny commented 1 year ago

@aehlke

I'm not sure I fully understand your use case.
Do you want to communicate using the SDK with the result iframe? This is currently not supported and might be a security concern.

However, the getCode method may give you access to the result page HTML and then you may run it in your isolated environment (e.g. sandboxed iframe) and communicate with it as you wish.

regarding your second question, yes, you can supply custom modules using the imports property of the configuration object.

you may supply a custom URL for the module. This uses import maps.
if you need to supply the source code of the module, you may generate a data URL from your source and use it.

Example:

import { createPlayground } from "livecodes";

const toDataURL = (content) => 'data:text/javascript;base64,' + btoa(content);

const moduleContent = `
export const sayHello = () => {
  console.log('hello');
};
`

const moduleUrl = toDataURL(moduleContent);
// console.log(moduleUrl);

const config = {
  imports: {
    'my-module': moduleUrl,  // this is your custom module
  },
  script: {
    language: 'javascript',
    content: 'import { sayHello } from "my-module";\nsayHello();'
  },
  activeEditor: 'script',
}

createPlayground("#container", { config, params: {console: 'open'} });

open in LiveCodes

I hope this helps.

aehlke commented 1 year ago

Yes, that helps clarify a lot. I appreciate it.

My use case for communicating with the results view is to let users script components within the app (you can think of chat bots for instance). I'd like users to be able to easily edit and run their scripts, rely on my app to pass data to them (via evaluateJavascript with frameInfo), and then to send data to the app via some postMessage mechanism (if not just presenting their own UI in the webview). For example, you could imagine a chatbot plugin which receives chat history as an input, and the output is used to output a new response widget with UI. Ideally I'd show your results view to allow editing the script easily, but perhaps I can use your results view only as a dev env and for tests and then export the HTML as you mention, I'll think it over more. Thanks again.

hatemhosny commented 1 year ago

Hi @aehlke

Now that the SDK is stable (https://livecodes.io/docs/sdk) and the officially supported headless mode (https://livecodes.io/docs/sdk/headless), shall we close this issue?

By the way, also check the newly added SDK method watch: https://livecodes.io/docs/sdk/js-ts#watch It can be useful to watch for console output, code changes, test results among other events.

aehlke commented 1 year ago

I spotted watch earlier today, looks very promising!

I've made good progress integrating your work and have it working for the app. I'll share the results once I have a TestFlight / public launch. Looking forward to going further. Thanks again for all the support.